From 20030ab60445b54fd2e1b53318d85e3ca03695b8 Mon Sep 17 00:00:00 2001 From: Manu Date: Tue, 24 Sep 2024 22:55:48 +0200 Subject: [PATCH] Add sensor platform to Bring integration (#126642) * Add sensor platform to Bring integration * Add more tests * unignore typedef check * Update language sensor * update snapshot * changes * add entities Co-authored-by: Joost Lekkerkerker * add units * lowercase * snapshot --------- Co-authored-by: Joost Lekkerkerker --- homeassistant/components/bring/__init__.py | 2 +- homeassistant/components/bring/const.py | 1 + homeassistant/components/bring/coordinator.py | 17 +- homeassistant/components/bring/entity.py | 1 - homeassistant/components/bring/icons.json | 14 + homeassistant/components/bring/sensor.py | 121 +++++ homeassistant/components/bring/strings.json | 38 ++ homeassistant/components/bring/todo.py | 9 +- homeassistant/components/bring/util.py | 40 ++ tests/components/bring/conftest.py | 3 + tests/components/bring/fixtures/items.json | 22 +- .../bring/fixtures/usersettings.json | 60 +++ .../bring/snapshots/test_sensor.ambr | 467 ++++++++++++++++++ tests/components/bring/test_init.py | 9 +- tests/components/bring/test_sensor.py | 44 ++ tests/components/bring/test_todo.py | 15 +- tests/components/bring/test_util.py | 56 +++ 17 files changed, 910 insertions(+), 9 deletions(-) create mode 100644 homeassistant/components/bring/sensor.py create mode 100644 homeassistant/components/bring/util.py create mode 100644 tests/components/bring/fixtures/usersettings.json create mode 100644 tests/components/bring/snapshots/test_sensor.ambr create mode 100644 tests/components/bring/test_sensor.py create mode 100644 tests/components/bring/test_util.py diff --git a/homeassistant/components/bring/__init__.py b/homeassistant/components/bring/__init__.py index f55e75c70bf..80b7a843cc0 100644 --- a/homeassistant/components/bring/__init__.py +++ b/homeassistant/components/bring/__init__.py @@ -20,7 +20,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN from .coordinator import BringDataUpdateCoordinator -PLATFORMS: list[Platform] = [Platform.TODO] +PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.TODO] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/bring/const.py b/homeassistant/components/bring/const.py index 911c08a835d..d44b7eb9423 100644 --- a/homeassistant/components/bring/const.py +++ b/homeassistant/components/bring/const.py @@ -9,3 +9,4 @@ ATTR_ITEM_NAME: Final = "item" ATTR_NOTIFICATION_TYPE: Final = "message" SERVICE_PUSH_NOTIFICATION = "send_message" +UNIT_ITEMS = "items" diff --git a/homeassistant/components/bring/coordinator.py b/homeassistant/components/bring/coordinator.py index 439eb552de4..7678213f117 100644 --- a/homeassistant/components/bring/coordinator.py +++ b/homeassistant/components/bring/coordinator.py @@ -11,7 +11,7 @@ from bring_api import ( BringParseException, BringRequestException, ) -from bring_api.types import BringItemsResponse, BringList +from bring_api.types import BringItemsResponse, BringList, BringUserSettingsResponse from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_EMAIL @@ -32,6 +32,7 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]): """A Bring Data Update Coordinator.""" config_entry: ConfigEntry + user_settings: BringUserSettingsResponse def __init__(self, hass: HomeAssistant, bring: Bring) -> None: """Initialize the Bring data coordinator.""" @@ -81,3 +82,17 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]): list_dict[lst["listUuid"]] = BringData(**lst, **items) return list_dict + + async def _async_setup(self) -> None: + """Set up coordinator.""" + + await self.async_refresh_user_settings() + + async def async_refresh_user_settings(self) -> None: + """Refresh user settings.""" + try: + self.user_settings = await self.bring.get_all_user_settings() + except (BringAuthException, BringRequestException, BringParseException) as e: + raise UpdateFailed( + "Unable to connect and retrieve user settings from bring" + ) from e diff --git a/homeassistant/components/bring/entity.py b/homeassistant/components/bring/entity.py index c5e0b84a190..5b6bf975764 100644 --- a/homeassistant/components/bring/entity.py +++ b/homeassistant/components/bring/entity.py @@ -23,7 +23,6 @@ class BringBaseEntity(CoordinatorEntity[BringDataUpdateCoordinator]): super().__init__(coordinator) self._list_uuid = bring_list["listUuid"] - self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{self._list_uuid}" self.device_info = DeviceInfo( entry_type=DeviceEntryType.SERVICE, diff --git a/homeassistant/components/bring/icons.json b/homeassistant/components/bring/icons.json index 6b79fab3c94..7a4775066cf 100644 --- a/homeassistant/components/bring/icons.json +++ b/homeassistant/components/bring/icons.json @@ -1,5 +1,19 @@ { "entity": { + "sensor": { + "urgent": { + "default": "mdi:run-fast" + }, + "discounted": { + "default": "mdi:brightness-percent" + }, + "convenient": { + "default": "mdi:fridge-outline" + }, + "list_language": { + "default": "mdi:earth" + } + }, "todo": { "shopping_list": { "default": "mdi:cart" diff --git a/homeassistant/components/bring/sensor.py b/homeassistant/components/bring/sensor.py new file mode 100644 index 00000000000..edc1da3d59b --- /dev/null +++ b/homeassistant/components/bring/sensor.py @@ -0,0 +1,121 @@ +"""Sensor platform for the Bring! integration.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from enum import StrEnum + +from bring_api import BringUserSettingsResponse +from bring_api.const import BRING_SUPPORTED_LOCALES + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, +) +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType + +from . import BringConfigEntry +from .const import UNIT_ITEMS +from .coordinator import BringData, BringDataUpdateCoordinator +from .entity import BringBaseEntity +from .util import list_language, sum_attributes + + +@dataclass(kw_only=True, frozen=True) +class BringSensorEntityDescription(SensorEntityDescription): + """Bring Sensor Description.""" + + value_fn: Callable[[BringData, BringUserSettingsResponse], StateType] + + +class BringSensor(StrEnum): + """Bring sensors.""" + + URGENT = "urgent" + CONVENIENT = "convenient" + DISCOUNTED = "discounted" + LIST_LANGUAGE = "list_language" + + +SENSOR_DESCRIPTIONS: tuple[BringSensorEntityDescription, ...] = ( + BringSensorEntityDescription( + key=BringSensor.URGENT, + translation_key=BringSensor.URGENT, + value_fn=lambda lst, _: sum_attributes(lst, "urgent"), + native_unit_of_measurement=UNIT_ITEMS, + ), + BringSensorEntityDescription( + key=BringSensor.CONVENIENT, + translation_key=BringSensor.CONVENIENT, + value_fn=lambda lst, _: sum_attributes(lst, "convenient"), + native_unit_of_measurement=UNIT_ITEMS, + ), + BringSensorEntityDescription( + key=BringSensor.DISCOUNTED, + translation_key=BringSensor.DISCOUNTED, + value_fn=lambda lst, _: sum_attributes(lst, "discounted"), + native_unit_of_measurement=UNIT_ITEMS, + ), + BringSensorEntityDescription( + key=BringSensor.LIST_LANGUAGE, + translation_key=BringSensor.LIST_LANGUAGE, + value_fn=( + lambda lst, settings: x.lower() + if (x := list_language(lst["listUuid"], settings)) + else None + ), + entity_category=EntityCategory.DIAGNOSTIC, + options=[x.lower() for x in BRING_SUPPORTED_LOCALES], + device_class=SensorDeviceClass.ENUM, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: BringConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the sensor platform.""" + coordinator = config_entry.runtime_data + + async_add_entities( + BringSensorEntity( + coordinator, + bring_list, + description, + ) + for description in SENSOR_DESCRIPTIONS + for bring_list in coordinator.data.values() + ) + + +class BringSensorEntity(BringBaseEntity, SensorEntity): + """A sensor entity.""" + + entity_description: BringSensorEntityDescription + + def __init__( + self, + coordinator: BringDataUpdateCoordinator, + bring_list: BringData, + entity_description: BringSensorEntityDescription, + ) -> None: + """Initialize the entity.""" + super().__init__(coordinator, bring_list) + self.entity_description = entity_description + self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{self._list_uuid}_{self.entity_description.key}" + + @property + def native_value(self) -> StateType: + """Return the state of the sensor.""" + + return self.entity_description.value_fn( + self.coordinator.data[self._list_uuid], + self.coordinator.user_settings, + ) diff --git a/homeassistant/components/bring/strings.json b/homeassistant/components/bring/strings.json index e3e700d75f9..8044e1b2637 100644 --- a/homeassistant/components/bring/strings.json +++ b/homeassistant/components/bring/strings.json @@ -26,6 +26,44 @@ "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } }, + "entity": { + "sensor": { + "urgent": { + "name": "Urgent" + }, + "convenient": { + "name": "On occasion" + }, + "discounted": { + "name": "Discount only" + }, + "list_language": { + "name": "Region & language", + "state": { + "de-at": "Austria", + "de-ch": "Switzerland (German)", + "de-de": "Germany", + "en-au": "Australia", + "en-ca": "Canada", + "en-gb": "United Kingdom", + "en-us": "United States", + "es-es": "Spain", + "fr-ch": "Switzerland (French)", + "fr-fr": "France", + "hu-hu": "Hungary", + "it-ch": "Switzerland (Italian)", + "it-it": "Italy", + "nb-no": "Norway", + "nl-nl": "Netherlands", + "pl-pl": "Poland", + "pt-br": "Portugal", + "ru-ru": "Russia", + "sv-se": "Sweden", + "tr-tr": "Turkey" + } + } + } + }, "exceptions": { "todo_save_item_failed": { "message": "Failed to save item {name} to Bring! list" diff --git a/homeassistant/components/bring/todo.py b/homeassistant/components/bring/todo.py index 97d7eba48bd..319aedc6b80 100644 --- a/homeassistant/components/bring/todo.py +++ b/homeassistant/components/bring/todo.py @@ -31,7 +31,7 @@ from .const import ( DOMAIN, SERVICE_PUSH_NOTIFICATION, ) -from .coordinator import BringData +from .coordinator import BringData, BringDataUpdateCoordinator from .entity import BringBaseEntity @@ -77,6 +77,13 @@ class BringTodoListEntity(BringBaseEntity, TodoListEntity): | TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM ) + def __init__( + self, coordinator: BringDataUpdateCoordinator, bring_list: BringData + ) -> None: + """Initialize the entity.""" + super().__init__(coordinator, bring_list) + self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{self._list_uuid}" + @property def todo_items(self) -> list[TodoItem]: """Return the todo items.""" diff --git a/homeassistant/components/bring/util.py b/homeassistant/components/bring/util.py new file mode 100644 index 00000000000..b706156a3d3 --- /dev/null +++ b/homeassistant/components/bring/util.py @@ -0,0 +1,40 @@ +"""Utility functions for Bring.""" + +from __future__ import annotations + +from bring_api import BringUserSettingsResponse + +from .coordinator import BringData + + +def list_language( + list_uuid: str, + user_settings: BringUserSettingsResponse, +) -> str | None: + """Get the lists language setting.""" + try: + list_settings = next( + filter( + lambda x: x["listUuid"] == list_uuid, + user_settings["userlistsettings"], + ) + ) + + return next( + filter( + lambda x: x["key"] == "listArticleLanguage", + list_settings["usersettings"], + ) + )["value"] + + except (StopIteration, KeyError): + return None + + +def sum_attributes(bring_list: BringData, attribute: str) -> int: + """Count items with given attribute set.""" + return sum( + item["attributes"][0]["content"][attribute] + for item in bring_list["purchase"] + if len(item.get("attributes", [])) + ) diff --git a/tests/components/bring/conftest.py b/tests/components/bring/conftest.py index 60c13a1c208..62aa38d4e92 100644 --- a/tests/components/bring/conftest.py +++ b/tests/components/bring/conftest.py @@ -46,6 +46,9 @@ def mock_bring_client() -> Generator[AsyncMock]: client.login.return_value = cast(BringAuthResponse, {"name": "Bring"}) client.load_lists.return_value = load_json_object_fixture("lists.json", DOMAIN) client.get_list.return_value = load_json_object_fixture("items.json", DOMAIN) + client.get_all_user_settings.return_value = load_json_object_fixture( + "usersettings.json", DOMAIN + ) yield client diff --git a/tests/components/bring/fixtures/items.json b/tests/components/bring/fixtures/items.json index 43e05a39fbb..e0b9006167b 100644 --- a/tests/components/bring/fixtures/items.json +++ b/tests/components/bring/fixtures/items.json @@ -6,13 +6,31 @@ "uuid": "b5d0790b-5f32-4d5c-91da-e29066f167de", "itemId": "Paprika", "specification": "Rot", - "attributes": [] + "attributes": [ + { + "type": "PURCHASE_CONDITIONS", + "content": { + "urgent": true, + "convenient": true, + "discounted": true + } + } + ] }, { "uuid": "72d370ab-d8ca-4e41-b956-91df94795b4e", "itemId": "Pouletbrüstli", "specification": "Bio", - "attributes": [] + "attributes": [ + { + "type": "PURCHASE_CONDITIONS", + "content": { + "urgent": true, + "convenient": true, + "discounted": true + } + } + ] } ], "recently": [ diff --git a/tests/components/bring/fixtures/usersettings.json b/tests/components/bring/fixtures/usersettings.json new file mode 100644 index 00000000000..6c93cdc7d83 --- /dev/null +++ b/tests/components/bring/fixtures/usersettings.json @@ -0,0 +1,60 @@ +{ + "userlistsettings": [ + { + "listUuid": "e542eef6-dba7-4c31-a52c-29e6ab9d83a5", + "usersettings": [ + { + "key": "listSectionOrder", + "value": "[\"Früchte & Gemüse\",\"Brot & Gebäck\",\"Milch & Käse\",\"Fleisch & Fisch\",\"Zutaten & Gewürze\",\"Fertig- & Tiefkühlprodukte\",\"Getreideprodukte\",\"Snacks & Süsswaren\",\"Getränke & Tabak\",\"Haushalt & Gesundheit\",\"Pflege & Gesundheit\",\"Tierbedarf\",\"Baumarkt & Garten\",\"Eigene Artikel\"]" + }, + { + "key": "listArticleLanguage", + "value": "de-DE" + } + ] + }, + { + "listUuid": "b4776778-7f6c-496e-951b-92a35d3db0dd", + "usersettings": [ + { + "key": "listSectionOrder", + "value": "[\"Früchte & Gemüse\",\"Brot & Gebäck\",\"Milch & Käse\",\"Fleisch & Fisch\",\"Zutaten & Gewürze\",\"Fertig- & Tiefkühlprodukte\",\"Getreideprodukte\",\"Snacks & Süsswaren\",\"Getränke & Tabak\",\"Haushalt & Gesundheit\",\"Pflege & Gesundheit\",\"Tierbedarf\",\"Baumarkt & Garten\",\"Eigene Artikel\"]" + }, + { + "key": "listArticleLanguage", + "value": "en-US" + } + ] + } + ], + "usersettings": [ + { + "key": "autoPush", + "value": "ON" + }, + { + "key": "premiumHideOffersBadge", + "value": "ON" + }, + { + "key": "premiumHideSponsoredCategories", + "value": "ON" + }, + { + "key": "premiumHideInspirationsBadge", + "value": "ON" + }, + { + "key": "onboardClient", + "value": "android" + }, + { + "key": "premiumHideOffersOnMain", + "value": "ON" + }, + { + "key": "defaultListUUID", + "value": "e542eef6-dba7-4c31-a52c-29e6ab9d83a5" + } + ] +} diff --git a/tests/components/bring/snapshots/test_sensor.ambr b/tests/components/bring/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..08e554632e9 --- /dev/null +++ b/tests/components/bring/snapshots/test_sensor.ambr @@ -0,0 +1,467 @@ +# serializer version: 1 +# name: test_setup[sensor.baumarkt_discount_only-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.baumarkt_discount_only', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Discount only', + 'platform': 'bring', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '00000000-00000000-00000000-00000000_b4776778-7f6c-496e-951b-92a35d3db0dd_discounted', + 'unit_of_measurement': 'items', + }) +# --- +# name: test_setup[sensor.baumarkt_discount_only-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Baumarkt Discount only', + 'unit_of_measurement': 'items', + }), + 'context': , + 'entity_id': 'sensor.baumarkt_discount_only', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_setup[sensor.baumarkt_on_occasion-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.baumarkt_on_occasion', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'On occasion', + 'platform': 'bring', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '00000000-00000000-00000000-00000000_b4776778-7f6c-496e-951b-92a35d3db0dd_convenient', + 'unit_of_measurement': 'items', + }) +# --- +# name: test_setup[sensor.baumarkt_on_occasion-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Baumarkt On occasion', + 'unit_of_measurement': 'items', + }), + 'context': , + 'entity_id': 'sensor.baumarkt_on_occasion', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_setup[sensor.baumarkt_region_language-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'de-at', + 'de-ch', + 'de-de', + 'en-au', + 'en-ca', + 'en-gb', + 'en-us', + 'es-es', + 'fr-ch', + 'fr-fr', + 'hu-hu', + 'it-ch', + 'it-it', + 'nb-no', + 'nl-nl', + 'pl-pl', + 'pt-br', + 'ru-ru', + 'sv-se', + 'tr-tr', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.baumarkt_region_language', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Region & language', + 'platform': 'bring', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '00000000-00000000-00000000-00000000_b4776778-7f6c-496e-951b-92a35d3db0dd_list_language', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup[sensor.baumarkt_region_language-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Baumarkt Region & language', + 'options': list([ + 'de-at', + 'de-ch', + 'de-de', + 'en-au', + 'en-ca', + 'en-gb', + 'en-us', + 'es-es', + 'fr-ch', + 'fr-fr', + 'hu-hu', + 'it-ch', + 'it-it', + 'nb-no', + 'nl-nl', + 'pl-pl', + 'pt-br', + 'ru-ru', + 'sv-se', + 'tr-tr', + ]), + }), + 'context': , + 'entity_id': 'sensor.baumarkt_region_language', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'en-us', + }) +# --- +# name: test_setup[sensor.baumarkt_urgent-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.baumarkt_urgent', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Urgent', + 'platform': 'bring', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '00000000-00000000-00000000-00000000_b4776778-7f6c-496e-951b-92a35d3db0dd_urgent', + 'unit_of_measurement': 'items', + }) +# --- +# name: test_setup[sensor.baumarkt_urgent-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Baumarkt Urgent', + 'unit_of_measurement': 'items', + }), + 'context': , + 'entity_id': 'sensor.baumarkt_urgent', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_setup[sensor.einkauf_discount_only-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.einkauf_discount_only', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Discount only', + 'platform': 'bring', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '00000000-00000000-00000000-00000000_e542eef6-dba7-4c31-a52c-29e6ab9d83a5_discounted', + 'unit_of_measurement': 'items', + }) +# --- +# name: test_setup[sensor.einkauf_discount_only-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Einkauf Discount only', + 'unit_of_measurement': 'items', + }), + 'context': , + 'entity_id': 'sensor.einkauf_discount_only', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_setup[sensor.einkauf_on_occasion-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.einkauf_on_occasion', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'On occasion', + 'platform': 'bring', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '00000000-00000000-00000000-00000000_e542eef6-dba7-4c31-a52c-29e6ab9d83a5_convenient', + 'unit_of_measurement': 'items', + }) +# --- +# name: test_setup[sensor.einkauf_on_occasion-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Einkauf On occasion', + 'unit_of_measurement': 'items', + }), + 'context': , + 'entity_id': 'sensor.einkauf_on_occasion', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- +# name: test_setup[sensor.einkauf_region_language-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'de-at', + 'de-ch', + 'de-de', + 'en-au', + 'en-ca', + 'en-gb', + 'en-us', + 'es-es', + 'fr-ch', + 'fr-fr', + 'hu-hu', + 'it-ch', + 'it-it', + 'nb-no', + 'nl-nl', + 'pl-pl', + 'pt-br', + 'ru-ru', + 'sv-se', + 'tr-tr', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.einkauf_region_language', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Region & language', + 'platform': 'bring', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '00000000-00000000-00000000-00000000_e542eef6-dba7-4c31-a52c-29e6ab9d83a5_list_language', + 'unit_of_measurement': None, + }) +# --- +# name: test_setup[sensor.einkauf_region_language-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Einkauf Region & language', + 'options': list([ + 'de-at', + 'de-ch', + 'de-de', + 'en-au', + 'en-ca', + 'en-gb', + 'en-us', + 'es-es', + 'fr-ch', + 'fr-fr', + 'hu-hu', + 'it-ch', + 'it-it', + 'nb-no', + 'nl-nl', + 'pl-pl', + 'pt-br', + 'ru-ru', + 'sv-se', + 'tr-tr', + ]), + }), + 'context': , + 'entity_id': 'sensor.einkauf_region_language', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'de-de', + }) +# --- +# name: test_setup[sensor.einkauf_urgent-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.einkauf_urgent', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Urgent', + 'platform': 'bring', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': , + 'unique_id': '00000000-00000000-00000000-00000000_e542eef6-dba7-4c31-a52c-29e6ab9d83a5_urgent', + 'unit_of_measurement': 'items', + }) +# --- +# name: test_setup[sensor.einkauf_urgent-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Einkauf Urgent', + 'unit_of_measurement': 'items', + }), + 'context': , + 'entity_id': 'sensor.einkauf_urgent', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- diff --git a/tests/components/bring/test_init.py b/tests/components/bring/test_init.py index 613b65e38b6..5ee66999ea4 100644 --- a/tests/components/bring/test_init.py +++ b/tests/components/bring/test_init.py @@ -90,7 +90,14 @@ async def test_init_exceptions( @pytest.mark.parametrize("exception", [BringRequestException, BringParseException]) -@pytest.mark.parametrize("bring_method", ["load_lists", "get_list"]) +@pytest.mark.parametrize( + "bring_method", + [ + "load_lists", + "get_list", + "get_all_user_settings", + ], +) async def test_config_entry_not_ready( hass: HomeAssistant, bring_config_entry: MockConfigEntry, diff --git a/tests/components/bring/test_sensor.py b/tests/components/bring/test_sensor.py new file mode 100644 index 00000000000..a36b0163165 --- /dev/null +++ b/tests/components/bring/test_sensor.py @@ -0,0 +1,44 @@ +"""Test for sensor platform of the Bring! integration.""" + +from collections.abc import Generator +from unittest.mock import patch + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.fixture(autouse=True) +def sensor_only() -> Generator[None]: + """Enable only the sensor platform.""" + with patch( + "homeassistant.components.bring.PLATFORMS", + [Platform.SENSOR], + ): + yield + + +@pytest.mark.usefixtures("mock_bring_client") +async def test_setup( + hass: HomeAssistant, + bring_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, +) -> None: + """Snapshot test states of sensor platform.""" + + bring_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(bring_config_entry.entry_id) + await hass.async_block_till_done() + + assert bring_config_entry.state is ConfigEntryState.LOADED + + await snapshot_platform( + hass, entity_registry, snapshot, bring_config_entry.entry_id + ) diff --git a/tests/components/bring/test_todo.py b/tests/components/bring/test_todo.py index d67429e8f49..9cc4ae3d888 100644 --- a/tests/components/bring/test_todo.py +++ b/tests/components/bring/test_todo.py @@ -1,7 +1,8 @@ """Test for todo platform of the Bring! integration.""" +from collections.abc import Generator import re -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, patch from bring_api import BringItemOperation, BringRequestException import pytest @@ -15,7 +16,7 @@ from homeassistant.components.todo import ( TodoServices, ) from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er @@ -23,6 +24,16 @@ from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry, snapshot_platform +@pytest.fixture(autouse=True) +def todo_only() -> Generator[None]: + """Enable only the todo platform.""" + with patch( + "homeassistant.components.bring.PLATFORMS", + [Platform.TODO], + ): + yield + + @pytest.mark.usefixtures("mock_bring_client") async def test_todo( hass: HomeAssistant, diff --git a/tests/components/bring/test_util.py b/tests/components/bring/test_util.py new file mode 100644 index 00000000000..0d9ed0c5345 --- /dev/null +++ b/tests/components/bring/test_util.py @@ -0,0 +1,56 @@ +"""Test for utility functions of the Bring! integration.""" + +from typing import cast + +from bring_api import BringUserSettingsResponse +import pytest + +from homeassistant.components.bring import DOMAIN +from homeassistant.components.bring.coordinator import BringData +from homeassistant.components.bring.util import list_language, sum_attributes + +from tests.common import load_json_object_fixture + + +@pytest.mark.parametrize( + ("list_uuid", "expected"), + [ + ("e542eef6-dba7-4c31-a52c-29e6ab9d83a5", "de-DE"), + ("b4776778-7f6c-496e-951b-92a35d3db0dd", "en-US"), + ("00000000-0000-0000-0000-00000000", None), + ], +) +def test_list_language(list_uuid: str, expected: str | None) -> None: + """Test function list_language.""" + + result = list_language( + list_uuid, + cast( + BringUserSettingsResponse, + load_json_object_fixture("usersettings.json", DOMAIN), + ), + ) + + assert result == expected + + +@pytest.mark.parametrize( + ("attribute", "expected"), + [ + ("urgent", 2), + ("convenient", 2), + ("discounted", 2), + ], +) +def test_sum_attributes(attribute: str, expected: int) -> None: + """Test function sum_attributes.""" + + result = sum_attributes( + cast( + BringData, + load_json_object_fixture("items.json", DOMAIN), + ), + attribute, + ) + + assert result == expected