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 <joostlek@outlook.com>

* add units

* lowercase

* snapshot

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Manu 2024-09-24 22:55:48 +02:00 committed by GitHub
parent c66e2dc076
commit 20030ab604
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 910 additions and 9 deletions

View file

@ -20,7 +20,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN from .const import DOMAIN
from .coordinator import BringDataUpdateCoordinator from .coordinator import BringDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.TODO] PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.TODO]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View file

@ -9,3 +9,4 @@ ATTR_ITEM_NAME: Final = "item"
ATTR_NOTIFICATION_TYPE: Final = "message" ATTR_NOTIFICATION_TYPE: Final = "message"
SERVICE_PUSH_NOTIFICATION = "send_message" SERVICE_PUSH_NOTIFICATION = "send_message"
UNIT_ITEMS = "items"

View file

@ -11,7 +11,7 @@ from bring_api import (
BringParseException, BringParseException,
BringRequestException, BringRequestException,
) )
from bring_api.types import BringItemsResponse, BringList from bring_api.types import BringItemsResponse, BringList, BringUserSettingsResponse
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL from homeassistant.const import CONF_EMAIL
@ -32,6 +32,7 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
"""A Bring Data Update Coordinator.""" """A Bring Data Update Coordinator."""
config_entry: ConfigEntry config_entry: ConfigEntry
user_settings: BringUserSettingsResponse
def __init__(self, hass: HomeAssistant, bring: Bring) -> None: def __init__(self, hass: HomeAssistant, bring: Bring) -> None:
"""Initialize the Bring data coordinator.""" """Initialize the Bring data coordinator."""
@ -81,3 +82,17 @@ class BringDataUpdateCoordinator(DataUpdateCoordinator[dict[str, BringData]]):
list_dict[lst["listUuid"]] = BringData(**lst, **items) list_dict[lst["listUuid"]] = BringData(**lst, **items)
return list_dict 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

View file

@ -23,7 +23,6 @@ class BringBaseEntity(CoordinatorEntity[BringDataUpdateCoordinator]):
super().__init__(coordinator) super().__init__(coordinator)
self._list_uuid = bring_list["listUuid"] self._list_uuid = bring_list["listUuid"]
self._attr_unique_id = f"{coordinator.config_entry.unique_id}_{self._list_uuid}"
self.device_info = DeviceInfo( self.device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE, entry_type=DeviceEntryType.SERVICE,

View file

@ -1,5 +1,19 @@
{ {
"entity": { "entity": {
"sensor": {
"urgent": {
"default": "mdi:run-fast"
},
"discounted": {
"default": "mdi:brightness-percent"
},
"convenient": {
"default": "mdi:fridge-outline"
},
"list_language": {
"default": "mdi:earth"
}
},
"todo": { "todo": {
"shopping_list": { "shopping_list": {
"default": "mdi:cart" "default": "mdi:cart"

View file

@ -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,
)

View file

@ -26,6 +26,44 @@
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" "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": { "exceptions": {
"todo_save_item_failed": { "todo_save_item_failed": {
"message": "Failed to save item {name} to Bring! list" "message": "Failed to save item {name} to Bring! list"

View file

@ -31,7 +31,7 @@ from .const import (
DOMAIN, DOMAIN,
SERVICE_PUSH_NOTIFICATION, SERVICE_PUSH_NOTIFICATION,
) )
from .coordinator import BringData from .coordinator import BringData, BringDataUpdateCoordinator
from .entity import BringBaseEntity from .entity import BringBaseEntity
@ -77,6 +77,13 @@ class BringTodoListEntity(BringBaseEntity, TodoListEntity):
| TodoListEntityFeature.SET_DESCRIPTION_ON_ITEM | 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 @property
def todo_items(self) -> list[TodoItem]: def todo_items(self) -> list[TodoItem]:
"""Return the todo items.""" """Return the todo items."""

View file

@ -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", []))
)

View file

@ -46,6 +46,9 @@ def mock_bring_client() -> Generator[AsyncMock]:
client.login.return_value = cast(BringAuthResponse, {"name": "Bring"}) client.login.return_value = cast(BringAuthResponse, {"name": "Bring"})
client.load_lists.return_value = load_json_object_fixture("lists.json", DOMAIN) 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_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 yield client

View file

@ -6,13 +6,31 @@
"uuid": "b5d0790b-5f32-4d5c-91da-e29066f167de", "uuid": "b5d0790b-5f32-4d5c-91da-e29066f167de",
"itemId": "Paprika", "itemId": "Paprika",
"specification": "Rot", "specification": "Rot",
"attributes": [] "attributes": [
{
"type": "PURCHASE_CONDITIONS",
"content": {
"urgent": true,
"convenient": true,
"discounted": true
}
}
]
}, },
{ {
"uuid": "72d370ab-d8ca-4e41-b956-91df94795b4e", "uuid": "72d370ab-d8ca-4e41-b956-91df94795b4e",
"itemId": "Pouletbrüstli", "itemId": "Pouletbrüstli",
"specification": "Bio", "specification": "Bio",
"attributes": [] "attributes": [
{
"type": "PURCHASE_CONDITIONS",
"content": {
"urgent": true,
"convenient": true,
"discounted": true
}
}
]
} }
], ],
"recently": [ "recently": [

View file

@ -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"
}
]
}

View file

@ -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': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.baumarkt_discount_only',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <BringSensor.DISCOUNTED: 'discounted'>,
'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': <ANY>,
'entity_id': 'sensor.baumarkt_discount_only',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2',
})
# ---
# name: test_setup[sensor.baumarkt_on_occasion-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.baumarkt_on_occasion',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <BringSensor.CONVENIENT: 'convenient'>,
'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': <ANY>,
'entity_id': 'sensor.baumarkt_on_occasion',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'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': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.baumarkt_region_language',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Region & language',
'platform': 'bring',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <BringSensor.LIST_LANGUAGE: 'list_language'>,
'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': <ANY>,
'entity_id': 'sensor.baumarkt_region_language',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'en-us',
})
# ---
# name: test_setup[sensor.baumarkt_urgent-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.baumarkt_urgent',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <BringSensor.URGENT: 'urgent'>,
'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': <ANY>,
'entity_id': 'sensor.baumarkt_urgent',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2',
})
# ---
# name: test_setup[sensor.einkauf_discount_only-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.einkauf_discount_only',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <BringSensor.DISCOUNTED: 'discounted'>,
'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': <ANY>,
'entity_id': 'sensor.einkauf_discount_only',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2',
})
# ---
# name: test_setup[sensor.einkauf_on_occasion-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.einkauf_on_occasion',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <BringSensor.CONVENIENT: 'convenient'>,
'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': <ANY>,
'entity_id': 'sensor.einkauf_on_occasion',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'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': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.einkauf_region_language',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Region & language',
'platform': 'bring',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': <BringSensor.LIST_LANGUAGE: 'list_language'>,
'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': <ANY>,
'entity_id': 'sensor.einkauf_region_language',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'de-de',
})
# ---
# name: test_setup[sensor.einkauf_urgent-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.einkauf_urgent',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <BringSensor.URGENT: 'urgent'>,
'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': <ANY>,
'entity_id': 'sensor.einkauf_urgent',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '2',
})
# ---

View file

@ -90,7 +90,14 @@ async def test_init_exceptions(
@pytest.mark.parametrize("exception", [BringRequestException, BringParseException]) @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( async def test_config_entry_not_ready(
hass: HomeAssistant, hass: HomeAssistant,
bring_config_entry: MockConfigEntry, bring_config_entry: MockConfigEntry,

View file

@ -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
)

View file

@ -1,7 +1,8 @@
"""Test for todo platform of the Bring! integration.""" """Test for todo platform of the Bring! integration."""
from collections.abc import Generator
import re import re
from unittest.mock import AsyncMock from unittest.mock import AsyncMock, patch
from bring_api import BringItemOperation, BringRequestException from bring_api import BringItemOperation, BringRequestException
import pytest import pytest
@ -15,7 +16,7 @@ from homeassistant.components.todo import (
TodoServices, TodoServices,
) )
from homeassistant.config_entries import ConfigEntryState 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.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er 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 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") @pytest.mark.usefixtures("mock_bring_client")
async def test_todo( async def test_todo(
hass: HomeAssistant, hass: HomeAssistant,

View file

@ -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