diff --git a/homeassistant/components/seventeentrack/__init__.py b/homeassistant/components/seventeentrack/__init__.py index 183d1bd4068..1f9879cdcbc 100644 --- a/homeassistant/components/seventeentrack/__init__.py +++ b/homeassistant/components/seventeentrack/__init__.py @@ -10,8 +10,9 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import DOMAIN +from .coordinator import SeventeenTrackCoordinator -PLATFORMS = [Platform.SENSOR] +PLATFORMS: list[Platform] = [Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -25,8 +26,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except SeventeenTrackError as err: raise ConfigEntryNotReady from err - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = client + coordinator = SeventeenTrackCoordinator(hass, client) + await coordinator.async_config_entry_first_refresh() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - return True diff --git a/homeassistant/components/seventeentrack/const.py b/homeassistant/components/seventeentrack/const.py index 6f8ae1b221c..fc7ca7b2e7f 100644 --- a/homeassistant/components/seventeentrack/const.py +++ b/homeassistant/components/seventeentrack/const.py @@ -1,6 +1,9 @@ """Constants for the 17track.net component.""" from datetime import timedelta +import logging + +LOGGER = logging.getLogger(__package__) ATTR_DESTINATION_COUNTRY = "destination_country" ATTR_INFO_TEXT = "info_text" diff --git a/homeassistant/components/seventeentrack/coordinator.py b/homeassistant/components/seventeentrack/coordinator.py new file mode 100644 index 00000000000..84bdf1e1359 --- /dev/null +++ b/homeassistant/components/seventeentrack/coordinator.py @@ -0,0 +1,84 @@ +"""Coordinator for 17Track.""" + +from dataclasses import dataclass +from typing import Any + +from py17track import Client as SeventeenTrackClient +from py17track.errors import SeventeenTrackError +from py17track.package import Package + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.util import slugify + +from .const import ( + CONF_SHOW_ARCHIVED, + CONF_SHOW_DELIVERED, + DEFAULT_SCAN_INTERVAL, + DOMAIN, + LOGGER, +) + + +@dataclass +class SeventeenTrackData: + """Class for handling the data retrieval.""" + + summary: dict[str, dict[str, Any]] + live_packages: dict[str, Package] + + +class SeventeenTrackCoordinator(DataUpdateCoordinator[SeventeenTrackData]): + """Class to manage fetching 17Track data.""" + + config_entry: ConfigEntry + + def __init__(self, hass: HomeAssistant, client: SeventeenTrackClient) -> None: + """Initialize.""" + super().__init__( + hass, + LOGGER, + name=DOMAIN, + update_interval=DEFAULT_SCAN_INTERVAL, + ) + self.show_delivered = self.config_entry.options[CONF_SHOW_DELIVERED] + self.account_id = client.profile.account_id + + self._show_archived = self.config_entry.options[CONF_SHOW_ARCHIVED] + self._client = client + + async def _async_update_data(self) -> SeventeenTrackData: + """Fetch data from 17Track API.""" + + try: + summary = await self._client.profile.summary( + show_archived=self._show_archived + ) + + live_packages = set( + await self._client.profile.packages(show_archived=self._show_archived) + ) + + except SeventeenTrackError as err: + raise UpdateFailed(err) from err + + summary_dict = {} + live_packages_dict = {} + + for status, quantity in summary.items(): + summary_dict[slugify(status)] = { + "quantity": quantity, + "packages": [], + "status_name": status, + } + + for package in live_packages: + live_packages_dict[package.tracking_number] = package + summary_value = summary_dict.get(slugify(package.status)) + if summary_value: + summary_value["packages"].append(package) + + return SeventeenTrackData( + summary=summary_dict, live_packages=live_packages_dict + ) diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 1de627fab39..cbad01d0b0a 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -2,10 +2,8 @@ from __future__ import annotations -import logging +from typing import Any -from py17track.errors import SeventeenTrackError -from py17track.package import Package import voluptuous as vol from homeassistant.components import persistent_notification @@ -17,15 +15,15 @@ from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, ) -from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant +from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback from homeassistant.data_entry_flow import FlowResultType -from homeassistant.helpers import config_validation as cv, entity, entity_registry as er +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.event import async_call_later from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType -from homeassistant.util import Throttle, slugify +from homeassistant.helpers.update_coordinator import CoordinatorEntity +from . import SeventeenTrackCoordinator from .const import ( ATTR_DESTINATION_COUNTRY, ATTR_INFO_TEXT, @@ -39,17 +37,15 @@ from .const import ( ATTRIBUTION, CONF_SHOW_ARCHIVED, CONF_SHOW_DELIVERED, - DEFAULT_SCAN_INTERVAL, DOMAIN, ENTITY_ID_TEMPLATE, + LOGGER, NOTIFICATION_DELIVERED_MESSAGE, NOTIFICATION_DELIVERED_TITLE, UNIQUE_ID_TEMPLATE, VALUE_DELIVERED, ) -_LOGGER = logging.getLogger(__name__) - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_USERNAME): cv.string, @@ -111,81 +107,155 @@ async def async_setup_entry( ) -> None: """Set up a 17Track sensor entry.""" - client = hass.data[DOMAIN][config_entry.entry_id] + coordinator: SeventeenTrackCoordinator = hass.data[DOMAIN][config_entry.entry_id] + previous_tracking_numbers: set[str] = set() - data = SeventeenTrackData( - client, - async_add_entities, - DEFAULT_SCAN_INTERVAL, - config_entry.options[CONF_SHOW_ARCHIVED], - config_entry.options[CONF_SHOW_DELIVERED], - str(hass.config.time_zone), + @callback + def _async_create_remove_entities(): + live_tracking_numbers = set(coordinator.data.live_packages.keys()) + + new_tracking_numbers = live_tracking_numbers - previous_tracking_numbers + old_tracking_numbers = previous_tracking_numbers - live_tracking_numbers + + previous_tracking_numbers.update(live_tracking_numbers) + + packages_to_add = [ + coordinator.data.live_packages[tracking_number] + for tracking_number in new_tracking_numbers + ] + + for package_data in coordinator.data.live_packages.values(): + if ( + package_data.status == VALUE_DELIVERED + and not coordinator.show_delivered + ): + old_tracking_numbers.add(package_data.tracking_number) + notify_delivered( + hass, + package_data.friendly_name, + package_data.tracking_number, + ) + + remove_packages(hass, coordinator.account_id, old_tracking_numbers) + + async_add_entities( + SeventeenTrackPackageSensor( + coordinator, + package_data.tracking_number, + ) + for package_data in packages_to_add + if not ( + not coordinator.show_delivered and package_data.status == "Delivered" + ) + ) + + async_add_entities( + SeventeenTrackSummarySensor(status, summary_data["status_name"], coordinator) + for status, summary_data in coordinator.data.summary.items() + ) + + _async_create_remove_entities() + + config_entry.async_on_unload( + coordinator.async_add_listener(_async_create_remove_entities) ) - await data.async_update() -class SeventeenTrackSummarySensor(SensorEntity): +class SeventeenTrackSummarySensor( + CoordinatorEntity[SeventeenTrackCoordinator], SensorEntity +): """Define a summary sensor.""" _attr_attribution = ATTRIBUTION _attr_icon = "mdi:package" _attr_native_unit_of_measurement = "packages" - def __init__(self, data, status, initial_state) -> None: - """Initialize.""" - self._attr_extra_state_attributes = {} - self._data = data - self._state = initial_state + def __init__( + self, + status: str, + status_name: str, + coordinator: SeventeenTrackCoordinator, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator) self._status = status - self._attr_name = f"Seventeentrack Packages {status}" - self._attr_unique_id = f"summary_{data.account_id}_{slugify(status)}" + self._attr_name = f"Seventeentrack Packages {status_name}" + self._attr_unique_id = f"summary_{coordinator.account_id}_{self._status}" @property def available(self) -> bool: """Return whether the entity is available.""" - return self._state is not None + return self._status in self.coordinator.data.summary @property def native_value(self) -> StateType: - """Return the state.""" - return self._state + """Return the state of the sensor.""" + return self.coordinator.data.summary[self._status]["quantity"] - async def async_update(self) -> None: - """Update the sensor.""" - await self._data.async_update() - - package_data = [] - for package in self._data.packages.values(): - if package.status != self._status: - continue - - package_data.append( + @property + def extra_state_attributes(self) -> dict[str, Any] | None: + """Return the state attributes.""" + packages = self.coordinator.data.summary[self._status]["packages"] + return { + ATTR_PACKAGES: [ { - ATTR_FRIENDLY_NAME: package.friendly_name, - ATTR_INFO_TEXT: package.info_text, - ATTR_TIMESTAMP: package.timestamp, - ATTR_STATUS: package.status, - ATTR_LOCATION: package.location, ATTR_TRACKING_NUMBER: package.tracking_number, + ATTR_LOCATION: package.location, + ATTR_STATUS: package.status, + ATTR_TIMESTAMP: package.timestamp, + ATTR_INFO_TEXT: package.info_text, + ATTR_FRIENDLY_NAME: package.friendly_name, } - ) - - self._attr_extra_state_attributes[ATTR_PACKAGES] = ( - package_data if package_data else None - ) - - self._state = self._data.summary.get(self._status) + for package in packages + ] + } -class SeventeenTrackPackageSensor(SensorEntity): +class SeventeenTrackPackageSensor( + CoordinatorEntity[SeventeenTrackCoordinator], SensorEntity +): """Define an individual package sensor.""" _attr_attribution = ATTRIBUTION _attr_icon = "mdi:package" - def __init__(self, data, package) -> None: - """Initialize.""" - self._attr_extra_state_attributes = { + def __init__( + self, + coordinator: SeventeenTrackCoordinator, + tracking_number: str, + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator) + self._tracking_number = tracking_number + self._previous_status = coordinator.data.live_packages[tracking_number].status + self.entity_id = ENTITY_ID_TEMPLATE.format(tracking_number) + self._attr_unique_id = UNIQUE_ID_TEMPLATE.format( + coordinator.account_id, tracking_number + ) + + @property + def available(self) -> bool: + """Return whether the entity is available.""" + return self._tracking_number in self.coordinator.data.live_packages + + @property + def name(self) -> str: + """Return the name.""" + package = self.coordinator.data.live_packages.get(self._tracking_number) + if package is None or not (name := package.friendly_name): + name = self._tracking_number + return f"Seventeentrack Package: {name}" + + @property + def native_value(self) -> StateType: + """Return the state.""" + return self.coordinator.data.live_packages[self._tracking_number].status + + @property + def extra_state_attributes(self) -> dict[str, Any] | None: + """Return the state attributes.""" + package = self.coordinator.data.live_packages[self._tracking_number] + return { ATTR_DESTINATION_COUNTRY: package.destination_country, ATTR_INFO_TEXT: package.info_text, ATTR_TIMESTAMP: package.timestamp, @@ -195,158 +265,30 @@ class SeventeenTrackPackageSensor(SensorEntity): ATTR_TRACKING_INFO_LANGUAGE: package.tracking_info_language, ATTR_TRACKING_NUMBER: package.tracking_number, } - self._data = data - self._friendly_name = package.friendly_name - self._state = package.status - self._tracking_number = package.tracking_number - self.entity_id = ENTITY_ID_TEMPLATE.format(self._tracking_number) - self._attr_unique_id = UNIQUE_ID_TEMPLATE.format( - data.account_id, self._tracking_number - ) - @property - def available(self) -> bool: - """Return whether the entity is available.""" - return self._data.packages.get(self._tracking_number) is not None - @property - def name(self) -> str: - """Return the name.""" - if not (name := self._friendly_name): - name = self._tracking_number - return f"Seventeentrack Package: {name}" - - @property - def native_value(self) -> StateType: - """Return the state.""" - return self._state - - async def async_update(self) -> None: - """Update the sensor.""" - await self._data.async_update() - - if not self.available: - # Entity cannot be removed while its being added - async_call_later(self.hass, 1, self._remove) - return - - package = self._data.packages.get(self._tracking_number, None) - - # If the user has elected to not see delivered packages and one gets - # delivered, post a notification: - if package.status == VALUE_DELIVERED and not self._data.show_delivered: - self._notify_delivered() - # Entity cannot be removed while its being added - async_call_later(self.hass, 1, self._remove) - return - - self._attr_extra_state_attributes.update( - { - ATTR_INFO_TEXT: package.info_text, - ATTR_TIMESTAMP: package.timestamp, - ATTR_LOCATION: package.location, - } - ) - self._state = package.status - self._friendly_name = package.friendly_name - - async def _remove(self, *_): - """Remove entity itself.""" - await self.async_remove(force_remove=True) - - reg = er.async_get(self.hass) +def remove_packages(hass: HomeAssistant, account_id: str, packages: set[str]) -> None: + """Remove entity itself.""" + reg = er.async_get(hass) + for package in packages: entity_id = reg.async_get_entity_id( "sensor", "seventeentrack", - UNIQUE_ID_TEMPLATE.format(self._data.account_id, self._tracking_number), + UNIQUE_ID_TEMPLATE.format(account_id, package), ) if entity_id: reg.async_remove(entity_id) - def _notify_delivered(self): - """Notify when package is delivered.""" - _LOGGER.info("Package delivered: %s", self._tracking_number) - identification = ( - self._friendly_name if self._friendly_name else self._tracking_number - ) - message = NOTIFICATION_DELIVERED_MESSAGE.format( - identification, self._tracking_number - ) - title = NOTIFICATION_DELIVERED_TITLE.format(identification) - notification_id = NOTIFICATION_DELIVERED_TITLE.format(self._tracking_number) +def notify_delivered(hass: HomeAssistant, friendly_name: str, tracking_number: str): + """Notify when package is delivered.""" + LOGGER.debug("Package delivered: %s", tracking_number) - persistent_notification.create( - self.hass, message, title=title, notification_id=notification_id - ) + identification = friendly_name if friendly_name else tracking_number + message = NOTIFICATION_DELIVERED_MESSAGE.format(identification, tracking_number) + title = NOTIFICATION_DELIVERED_TITLE.format(identification) + notification_id = NOTIFICATION_DELIVERED_TITLE.format(tracking_number) - -class SeventeenTrackData: - """Define a data handler for 17track.net.""" - - def __init__( - self, - client, - async_add_entities, - scan_interval, - show_archived, - show_delivered, - timezone, - ) -> None: - """Initialize.""" - self._async_add_entities = async_add_entities - self._client = client - self._scan_interval = scan_interval - self._show_archived = show_archived - self.account_id = client.profile.account_id - self.packages: dict[str, Package] = {} - self.show_delivered = show_delivered - self.timezone = timezone - self.summary: dict[str, int] = {} - self.async_update = Throttle(self._scan_interval)(self._async_update) - self.first_update = True - - async def _async_update(self): - """Get updated data from 17track.net.""" - entities: list[entity.Entity] = [] - - try: - packages = await self._client.profile.packages( - show_archived=self._show_archived, tz=self.timezone - ) - _LOGGER.debug("New package data received: %s", packages) - - new_packages = {p.tracking_number: p for p in packages} - - to_add = set(new_packages) - set(self.packages) - - _LOGGER.debug("Will add new tracking numbers: %s", to_add) - if to_add: - entities.extend( - SeventeenTrackPackageSensor(self, new_packages[tracking_number]) - for tracking_number in to_add - ) - - self.packages = new_packages - except SeventeenTrackError as err: - _LOGGER.error("There was an error retrieving packages: %s", err) - - try: - self.summary = await self._client.profile.summary( - show_archived=self._show_archived - ) - _LOGGER.debug("New summary data received: %s", self.summary) - - # creating summary sensors on first update - if self.first_update: - self.first_update = False - entities.extend( - SeventeenTrackSummarySensor(self, status, quantity) - for status, quantity in self.summary.items() - ) - - except SeventeenTrackError as err: - _LOGGER.error("There was an error retrieving the summary: %s", err) - self.summary = {} - - self._async_add_entities(entities, True) + persistent_notification.create( + hass, message, title=title, notification_id=notification_id + ) diff --git a/tests/components/seventeentrack/__init__.py b/tests/components/seventeentrack/__init__.py index 4101f34496e..b3452b38f96 100644 --- a/tests/components/seventeentrack/__init__.py +++ b/tests/components/seventeentrack/__init__.py @@ -4,7 +4,7 @@ from datetime import timedelta from freezegun.api import FrozenDateTimeFactory -from homeassistant.components.seventeentrack.sensor import DEFAULT_SCAN_INTERVAL +from homeassistant.components.seventeentrack.const import DEFAULT_SCAN_INTERVAL from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, async_fire_time_changed diff --git a/tests/components/seventeentrack/conftest.py b/tests/components/seventeentrack/conftest.py index 2865b3f2599..2e266a9b13c 100644 --- a/tests/components/seventeentrack/conftest.py +++ b/tests/components/seventeentrack/conftest.py @@ -7,12 +7,10 @@ from py17track.package import Package import pytest from homeassistant.components.seventeentrack.const import ( - DEFAULT_SHOW_ARCHIVED, - DEFAULT_SHOW_DELIVERED, -) -from homeassistant.components.seventeentrack.sensor import ( CONF_SHOW_ARCHIVED, CONF_SHOW_DELIVERED, + DEFAULT_SHOW_ARCHIVED, + DEFAULT_SHOW_DELIVERED, ) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME @@ -28,6 +26,8 @@ DEFAULT_SUMMARY = { "Returned": 0, } +DEFAULT_SUMMARY_LENGTH = len(DEFAULT_SUMMARY) + ACCOUNT_ID = "1234" NEW_SUMMARY_DATA = { diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index aa7f61ad318..27de64ca89f 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -14,6 +14,7 @@ from homeassistant.setup import async_setup_component from . import goto_future, init_integration from .conftest import ( DEFAULT_SUMMARY, + DEFAULT_SUMMARY_LENGTH, NEW_SUMMARY_DATA, VALID_PLATFORM_CONFIG_FULL, get_package, @@ -72,11 +73,10 @@ async def test_add_package( """Ensure package is added correctly when user add a new package.""" package = get_package() mock_seventeentrack.return_value.profile.packages.return_value = [package] - mock_seventeentrack.return_value.profile.summary.return_value = {} await init_integration(hass, mock_config_entry) - assert hass.states.get("sensor.seventeentrack_package_456") is not None - assert len(hass.states.async_entity_ids()) == 1 + assert hass.states.get("sensor.seventeentrack_package_456") + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1 package2 = get_package( tracking_number="789", @@ -90,7 +90,7 @@ async def test_add_package( await goto_future(hass, freezer) assert hass.states.get("sensor.seventeentrack_package_789") is not None - assert len(hass.states.async_entity_ids()) == 2 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 2 async def test_add_package_default_friendly_name( @@ -101,13 +101,12 @@ async def test_add_package_default_friendly_name( """Ensure package is added correctly with default friendly name when user add a new package without his own friendly name.""" package = get_package(friendly_name=None) mock_seventeentrack.return_value.profile.packages.return_value = [package] - mock_seventeentrack.return_value.profile.summary.return_value = {} await init_integration(hass, mock_config_entry) state_456 = hass.states.get("sensor.seventeentrack_package_456") assert state_456 is not None assert state_456.attributes["friendly_name"] == "Seventeentrack Package: 456" - assert len(hass.states.async_entity_ids()) == 1 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1 async def test_remove_package( @@ -130,26 +129,20 @@ async def test_remove_package( package1, package2, ] - mock_seventeentrack.return_value.profile.summary.return_value = {} await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is not None assert hass.states.get("sensor.seventeentrack_package_789") is not None - assert len(hass.states.async_entity_ids()) == 2 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 2 mock_seventeentrack.return_value.profile.packages.return_value = [package2] await goto_future(hass, freezer) - assert hass.states.get("sensor.seventeentrack_package_456").state == "unavailable" - assert len(hass.states.async_entity_ids()) == 2 - - await goto_future(hass, freezer) - assert hass.states.get("sensor.seventeentrack_package_456") is None assert hass.states.get("sensor.seventeentrack_package_789") is not None - assert len(hass.states.async_entity_ids()) == 1 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1 async def test_package_error( @@ -176,12 +169,11 @@ async def test_friendly_name_changed( """Test friendly name change.""" package = get_package() mock_seventeentrack.return_value.profile.packages.return_value = [package] - mock_seventeentrack.return_value.profile.summary.return_value = {} await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is not None - assert len(hass.states.async_entity_ids()) == 1 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1 package = get_package(friendly_name="friendly name 2") mock_seventeentrack.return_value.profile.packages.return_value = [package] @@ -193,7 +185,7 @@ async def test_friendly_name_changed( "sensor.seventeentrack_package_456" ) assert entity.name == "Seventeentrack Package: friendly name 2" - assert len(hass.states.async_entity_ids()) == 1 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1 async def test_delivered_not_shown( @@ -205,7 +197,6 @@ async def test_delivered_not_shown( """Ensure delivered packages are not shown.""" package = get_package(status=40) mock_seventeentrack.return_value.profile.packages.return_value = [package] - mock_seventeentrack.return_value.profile.summary.return_value = {} with patch( "homeassistant.components.seventeentrack.sensor.persistent_notification" @@ -213,7 +204,7 @@ async def test_delivered_not_shown( await init_integration(hass, mock_config_entry_with_default_options) await goto_future(hass, freezer) - assert not hass.states.async_entity_ids() + assert hass.states.get("sensor.seventeentrack_package_456") is None persistent_notification_mock.create.assert_called() @@ -225,7 +216,6 @@ async def test_delivered_shown( """Ensure delivered packages are show when user choose to show them.""" package = get_package(status=40) mock_seventeentrack.return_value.profile.packages.return_value = [package] - mock_seventeentrack.return_value.profile.summary.return_value = {} with patch( "homeassistant.components.seventeentrack.sensor.persistent_notification" @@ -233,7 +223,7 @@ async def test_delivered_shown( await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is not None - assert len(hass.states.async_entity_ids()) == 1 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1 persistent_notification_mock.create.assert_not_called() @@ -246,12 +236,11 @@ async def test_becomes_delivered_not_shown_notification( """Ensure notification is triggered when package becomes delivered.""" package = get_package() mock_seventeentrack.return_value.profile.packages.return_value = [package] - mock_seventeentrack.return_value.profile.summary.return_value = {} await init_integration(hass, mock_config_entry_with_default_options) assert hass.states.get("sensor.seventeentrack_package_456") is not None - assert len(hass.states.async_entity_ids()) == 1 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1 package_delivered = get_package(status=40) mock_seventeentrack.return_value.profile.packages.return_value = [package_delivered] @@ -260,10 +249,9 @@ async def test_becomes_delivered_not_shown_notification( "homeassistant.components.seventeentrack.sensor.persistent_notification" ) as persistent_notification_mock: await goto_future(hass, freezer) - await goto_future(hass, freezer) persistent_notification_mock.create.assert_called() - assert not hass.states.async_entity_ids() + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH async def test_summary_correctly_updated( @@ -275,11 +263,10 @@ async def test_summary_correctly_updated( """Ensure summary entities are not duplicated.""" package = get_package(status=30) mock_seventeentrack.return_value.profile.packages.return_value = [package] - mock_seventeentrack.return_value.profile.summary.return_value = DEFAULT_SUMMARY await init_integration(hass, mock_config_entry) - assert len(hass.states.async_entity_ids()) == 8 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1 state_ready_picked = hass.states.get( "sensor.seventeentrack_packages_ready_to_be_picked_up" @@ -290,10 +277,9 @@ async def test_summary_correctly_updated( mock_seventeentrack.return_value.profile.packages.return_value = [] mock_seventeentrack.return_value.profile.summary.return_value = NEW_SUMMARY_DATA - await goto_future(hass, freezer) await goto_future(hass, freezer) - assert len(hass.states.async_entity_ids()) == 7 + assert len(hass.states.async_entity_ids()) == len(NEW_SUMMARY_DATA) for state in hass.states.async_all(): assert state.state == "1" @@ -301,7 +287,7 @@ async def test_summary_correctly_updated( "sensor.seventeentrack_packages_ready_to_be_picked_up" ) assert state_ready_picked is not None - assert state_ready_picked.attributes["packages"] is None + assert len(state_ready_picked.attributes["packages"]) == 0 async def test_summary_error( @@ -318,7 +304,7 @@ async def test_summary_error( await init_integration(hass, mock_config_entry) - assert len(hass.states.async_entity_ids()) == 1 + assert len(hass.states.async_entity_ids()) == 0 assert ( hass.states.get("sensor.seventeentrack_packages_ready_to_be_picked_up") is None @@ -334,12 +320,11 @@ async def test_utc_timestamp( package = get_package(tz="Asia/Jakarta") mock_seventeentrack.return_value.profile.packages.return_value = [package] - mock_seventeentrack.return_value.profile.summary.return_value = {} await init_integration(hass, mock_config_entry) assert hass.states.get("sensor.seventeentrack_package_456") is not None - assert len(hass.states.async_entity_ids()) == 1 + assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1 state_456 = hass.states.get("sensor.seventeentrack_package_456") assert state_456 is not None assert str(state_456.attributes.get("timestamp")) == "2020-08-10 03:32:00+00:00"