Add unique id to Mealie config entry (#120816)

This commit is contained in:
Joost Lekkerkerker 2024-06-29 17:48:28 +02:00 committed by GitHub
parent 0ab7647fea
commit 25932dff28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 47 additions and 17 deletions

View File

@ -50,12 +50,9 @@ class MealieMealplanCalendarEntity(MealieEntity, CalendarEntity):
self, coordinator: MealieCoordinator, entry_type: MealplanEntryType self, coordinator: MealieCoordinator, entry_type: MealplanEntryType
) -> None: ) -> None:
"""Create the Calendar entity.""" """Create the Calendar entity."""
super().__init__(coordinator) super().__init__(coordinator, entry_type.name.lower())
self._entry_type = entry_type self._entry_type = entry_type
self._attr_translation_key = entry_type.name.lower() self._attr_translation_key = entry_type.name.lower()
self._attr_unique_id = (
f"{self.coordinator.config_entry.entry_id}_{entry_type.name.lower()}"
)
@property @property
def event(self) -> CalendarEvent | None: def event(self) -> CalendarEvent | None:

View File

@ -28,14 +28,13 @@ class MealieConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a flow initialized by the user.""" """Handle a flow initialized by the user."""
errors: dict[str, str] = {} errors: dict[str, str] = {}
if user_input: if user_input:
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
client = MealieClient( client = MealieClient(
user_input[CONF_HOST], user_input[CONF_HOST],
token=user_input[CONF_API_TOKEN], token=user_input[CONF_API_TOKEN],
session=async_get_clientsession(self.hass), session=async_get_clientsession(self.hass),
) )
try: try:
await client.get_mealplan_today() info = await client.get_user_info()
except MealieConnectionError: except MealieConnectionError:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except MealieAuthenticationError: except MealieAuthenticationError:
@ -44,6 +43,8 @@ class MealieConfigFlow(ConfigFlow, domain=DOMAIN):
LOGGER.exception("Unexpected error") LOGGER.exception("Unexpected error")
errors["base"] = "unknown" errors["base"] = "unknown"
else: else:
await self.async_set_unique_id(info.user_id)
self._abort_if_unique_id_configured()
return self.async_create_entry( return self.async_create_entry(
title="Mealie", title="Mealie",
data=user_input, data=user_input,

View File

@ -12,10 +12,13 @@ class MealieEntity(CoordinatorEntity[MealieCoordinator]):
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__(self, coordinator: MealieCoordinator) -> None: def __init__(self, coordinator: MealieCoordinator, key: str) -> None:
"""Initialize Mealie entity.""" """Initialize Mealie entity."""
super().__init__(coordinator) super().__init__(coordinator)
unique_id = coordinator.config_entry.unique_id
assert unique_id is not None
self._attr_unique_id = f"{unique_id}_{key}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.config_entry.entry_id)}, identifiers={(DOMAIN, unique_id)},
entry_type=DeviceEntryType.SERVICE, entry_type=DeviceEntryType.SERVICE,
) )

View File

@ -2,7 +2,7 @@
from unittest.mock import patch from unittest.mock import patch
from aiomealie import Mealplan, MealplanResponse from aiomealie import Mealplan, MealplanResponse, UserInfo
from mashumaro.codecs.orjson import ORJSONDecoder from mashumaro.codecs.orjson import ORJSONDecoder
import pytest import pytest
from typing_extensions import Generator from typing_extensions import Generator
@ -44,6 +44,9 @@ def mock_mealie_client() -> Generator[AsyncMock]:
client.get_mealplan_today.return_value = ORJSONDecoder(list[Mealplan]).decode( client.get_mealplan_today.return_value = ORJSONDecoder(list[Mealplan]).decode(
load_fixture("get_mealplan_today.json", DOMAIN) load_fixture("get_mealplan_today.json", DOMAIN)
) )
client.get_user_info.return_value = UserInfo.from_json(
load_fixture("users_self.json", DOMAIN)
)
yield client yield client
@ -55,4 +58,5 @@ def mock_config_entry() -> MockConfigEntry:
title="Mealie", title="Mealie",
data={CONF_HOST: "demo.mealie.io", CONF_API_TOKEN: "token"}, data={CONF_HOST: "demo.mealie.io", CONF_API_TOKEN: "token"},
entry_id="01J0BC4QM2YBRP6H5G933CETT7", entry_id="01J0BC4QM2YBRP6H5G933CETT7",
unique_id="bf1c62fe-4941-4332-9886-e54e88dbdba0",
) )

View File

@ -0,0 +1,24 @@
{
"id": "bf1c62fe-4941-4332-9886-e54e88dbdba0",
"username": "admin",
"fullName": "Change Me",
"email": "changeme@example.com",
"authMethod": "Mealie",
"admin": true,
"group": "home",
"advanced": true,
"canInvite": true,
"canManage": true,
"canOrganize": true,
"groupId": "24477569-f6af-4b53-9e3f-6d04b0ca6916",
"groupSlug": "home",
"tokens": [
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb25nX3Rva2VuIjp0cnVlLCJpZCI6ImJmMWM2MmZlLTQ5NDEtNDMzMi05ODg2LWU1NGU4OGRiZGJhMCIsIm5hbWUiOiJ0ZXN0aW5nIiwiaW50ZWdyYXRpb25faWQiOiJnZW5lcmljIiwiZXhwIjoxODczOTA5ODk4fQ.xwXZp4fL2g1RbIqGtBeOaS6RDfsYbQDHj8XtRM3wlX0",
"name": "testing",
"id": 2,
"createdAt": "2024-05-20T10:31:38.179669"
}
],
"cacheKey": "1234"
}

View File

@ -192,7 +192,7 @@
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': 0, 'supported_features': 0,
'translation_key': 'breakfast', 'translation_key': 'breakfast',
'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_breakfast', 'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_breakfast',
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---
@ -244,7 +244,7 @@
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': 0, 'supported_features': 0,
'translation_key': 'dinner', 'translation_key': 'dinner',
'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_dinner', 'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_dinner',
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---
@ -296,7 +296,7 @@
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': 0, 'supported_features': 0,
'translation_key': 'lunch', 'translation_key': 'lunch',
'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_lunch', 'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_lunch',
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---
@ -348,7 +348,7 @@
'previous_unique_id': None, 'previous_unique_id': None,
'supported_features': 0, 'supported_features': 0,
'translation_key': 'side', 'translation_key': 'side',
'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_side', 'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_side',
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---

View File

@ -13,7 +13,7 @@
'identifiers': set({ 'identifiers': set({
tuple( tuple(
'mealie', 'mealie',
'01J0BC4QM2YBRP6H5G933CETT7', 'bf1c62fe-4941-4332-9886-e54e88dbdba0',
), ),
}), }),
'is_new': False, 'is_new': False,

View File

@ -37,6 +37,7 @@ async def test_full_flow(
CONF_HOST: "demo.mealie.io", CONF_HOST: "demo.mealie.io",
CONF_API_TOKEN: "token", CONF_API_TOKEN: "token",
} }
assert result["result"].unique_id == "bf1c62fe-4941-4332-9886-e54e88dbdba0"
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -55,7 +56,7 @@ async def test_flow_errors(
error: str, error: str,
) -> None: ) -> None:
"""Test flow errors.""" """Test flow errors."""
mock_mealie_client.get_mealplan_today.side_effect = exception mock_mealie_client.get_user_info.side_effect = exception
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -72,7 +73,7 @@ async def test_flow_errors(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error} assert result["errors"] == {"base": error}
mock_mealie_client.get_mealplan_today.side_effect = None mock_mealie_client.get_user_info.side_effect = None
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],

View File

@ -26,7 +26,7 @@ async def test_device_info(
"""Test device registry integration.""" """Test device registry integration."""
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
device_entry = device_registry.async_get_device( device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_config_entry.entry_id)} identifiers={(DOMAIN, mock_config_entry.unique_id)}
) )
assert device_entry is not None assert device_entry is not None
assert device_entry == snapshot assert device_entry == snapshot