Add coordinator to 17Track (#115057)

* Add coordinator to 17Track

* Add coordinator to 17Track

remove SensorEntityDescription (different PR)

* Update homeassistant/components/seventeentrack/coordinator.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update homeassistant/components/seventeentrack/sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Add coordinator to 17Track

fix CR

* Add coordinator to 17Track

fix second CR

* Add coordinator to 17Track

remove commented out code + fix display name

* Add coordinator to 17Track

created a set outside _async_create_remove_entities function

* Add coordinator to 17Track

fix CR

* Add coordinator to 17Track

fix CR 2

* Update homeassistant/components/seventeentrack/coordinator.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Add coordinator to 17Track

raise UpdateFailed if API throws an exception

* Add coordinator to 17Track

merge calls

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Shai Ungar 2024-04-23 10:01:45 +03:00 committed by GitHub
parent b8f44fb722
commit e0c785b2b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 257 additions and 240 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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