diff --git a/homeassistant/components/mealie/calendar.py b/homeassistant/components/mealie/calendar.py index f3c692a1d12..fb628754f06 100644 --- a/homeassistant/components/mealie/calendar.py +++ b/homeassistant/components/mealie/calendar.py @@ -50,12 +50,9 @@ class MealieMealplanCalendarEntity(MealieEntity, CalendarEntity): self, coordinator: MealieCoordinator, entry_type: MealplanEntryType ) -> None: """Create the Calendar entity.""" - super().__init__(coordinator) + super().__init__(coordinator, entry_type.name.lower()) self._entry_type = entry_type self._attr_translation_key = entry_type.name.lower() - self._attr_unique_id = ( - f"{self.coordinator.config_entry.entry_id}_{entry_type.name.lower()}" - ) @property def event(self) -> CalendarEvent | None: diff --git a/homeassistant/components/mealie/config_flow.py b/homeassistant/components/mealie/config_flow.py index b25cade148a..550e4679720 100644 --- a/homeassistant/components/mealie/config_flow.py +++ b/homeassistant/components/mealie/config_flow.py @@ -28,14 +28,13 @@ class MealieConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a flow initialized by the user.""" errors: dict[str, str] = {} if user_input: - self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]}) client = MealieClient( user_input[CONF_HOST], token=user_input[CONF_API_TOKEN], session=async_get_clientsession(self.hass), ) try: - await client.get_mealplan_today() + info = await client.get_user_info() except MealieConnectionError: errors["base"] = "cannot_connect" except MealieAuthenticationError: @@ -44,6 +43,8 @@ class MealieConfigFlow(ConfigFlow, domain=DOMAIN): LOGGER.exception("Unexpected error") errors["base"] = "unknown" else: + await self.async_set_unique_id(info.user_id) + self._abort_if_unique_id_configured() return self.async_create_entry( title="Mealie", data=user_input, diff --git a/homeassistant/components/mealie/entity.py b/homeassistant/components/mealie/entity.py index 5e339c1d4b8..765ae2b99d7 100644 --- a/homeassistant/components/mealie/entity.py +++ b/homeassistant/components/mealie/entity.py @@ -12,10 +12,13 @@ class MealieEntity(CoordinatorEntity[MealieCoordinator]): _attr_has_entity_name = True - def __init__(self, coordinator: MealieCoordinator) -> None: + def __init__(self, coordinator: MealieCoordinator, key: str) -> None: """Initialize Mealie entity.""" 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( - identifiers={(DOMAIN, coordinator.config_entry.entry_id)}, + identifiers={(DOMAIN, unique_id)}, entry_type=DeviceEntryType.SERVICE, ) diff --git a/tests/components/mealie/conftest.py b/tests/components/mealie/conftest.py index dd6309cb524..9bda9e3c46d 100644 --- a/tests/components/mealie/conftest.py +++ b/tests/components/mealie/conftest.py @@ -2,7 +2,7 @@ from unittest.mock import patch -from aiomealie import Mealplan, MealplanResponse +from aiomealie import Mealplan, MealplanResponse, UserInfo from mashumaro.codecs.orjson import ORJSONDecoder import pytest 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( load_fixture("get_mealplan_today.json", DOMAIN) ) + client.get_user_info.return_value = UserInfo.from_json( + load_fixture("users_self.json", DOMAIN) + ) yield client @@ -55,4 +58,5 @@ def mock_config_entry() -> MockConfigEntry: title="Mealie", data={CONF_HOST: "demo.mealie.io", CONF_API_TOKEN: "token"}, entry_id="01J0BC4QM2YBRP6H5G933CETT7", + unique_id="bf1c62fe-4941-4332-9886-e54e88dbdba0", ) diff --git a/tests/components/mealie/fixtures/users_self.json b/tests/components/mealie/fixtures/users_self.json new file mode 100644 index 00000000000..6d5901c8cc0 --- /dev/null +++ b/tests/components/mealie/fixtures/users_self.json @@ -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" +} diff --git a/tests/components/mealie/snapshots/test_calendar.ambr b/tests/components/mealie/snapshots/test_calendar.ambr index 634d4963135..c3b26e1e9e2 100644 --- a/tests/components/mealie/snapshots/test_calendar.ambr +++ b/tests/components/mealie/snapshots/test_calendar.ambr @@ -192,7 +192,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'breakfast', - 'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_breakfast', + 'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_breakfast', 'unit_of_measurement': None, }) # --- @@ -244,7 +244,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'dinner', - 'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_dinner', + 'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_dinner', 'unit_of_measurement': None, }) # --- @@ -296,7 +296,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'lunch', - 'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_lunch', + 'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_lunch', 'unit_of_measurement': None, }) # --- @@ -348,7 +348,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'side', - 'unique_id': '01J0BC4QM2YBRP6H5G933CETT7_side', + 'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_side', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/mealie/snapshots/test_init.ambr b/tests/components/mealie/snapshots/test_init.ambr index 1333b292dac..8f800676945 100644 --- a/tests/components/mealie/snapshots/test_init.ambr +++ b/tests/components/mealie/snapshots/test_init.ambr @@ -13,7 +13,7 @@ 'identifiers': set({ tuple( 'mealie', - '01J0BC4QM2YBRP6H5G933CETT7', + 'bf1c62fe-4941-4332-9886-e54e88dbdba0', ), }), 'is_new': False, diff --git a/tests/components/mealie/test_config_flow.py b/tests/components/mealie/test_config_flow.py index ac68ed2fac5..777bb1e4ad1 100644 --- a/tests/components/mealie/test_config_flow.py +++ b/tests/components/mealie/test_config_flow.py @@ -37,6 +37,7 @@ async def test_full_flow( CONF_HOST: "demo.mealie.io", CONF_API_TOKEN: "token", } + assert result["result"].unique_id == "bf1c62fe-4941-4332-9886-e54e88dbdba0" @pytest.mark.parametrize( @@ -55,7 +56,7 @@ async def test_flow_errors( error: str, ) -> None: """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( DOMAIN, @@ -72,7 +73,7 @@ async def test_flow_errors( assert result["type"] is FlowResultType.FORM 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["flow_id"], diff --git a/tests/components/mealie/test_init.py b/tests/components/mealie/test_init.py index 7d63ad135f9..5a7a5387897 100644 --- a/tests/components/mealie/test_init.py +++ b/tests/components/mealie/test_init.py @@ -26,7 +26,7 @@ async def test_device_info( """Test device registry integration.""" await setup_integration(hass, mock_config_entry) 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 == snapshot