From 5b24497e27068d5a1ec58a4f8c708cc5b01b44bd Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Sun, 21 Mar 2021 12:16:21 +0100 Subject: [PATCH] Fixed #46 - GarbageContainerScheduleTile: optional pushbullet notification x days event --- .../tiles/GarbageContainerScheduleTile.py | 50 ++++++++++- .../tiles/TestGarbageContainerScheduleTile.py | 87 +++++++++++++++++++ 2 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 test/logic/tile/tiles/TestGarbageContainerScheduleTile.py diff --git a/src/logic/tile/tiles/GarbageContainerScheduleTile.py b/src/logic/tile/tiles/GarbageContainerScheduleTile.py index ceac49f..4a7ca89 100644 --- a/src/logic/tile/tiles/GarbageContainerScheduleTile.py +++ b/src/logic/tile/tiles/GarbageContainerScheduleTile.py @@ -5,6 +5,7 @@ from typing import Dict, List from flask import Blueprint from babel.dates import format_date +from logic import Helpers from logic.service.ServiceManager import ServiceManager from logic.service.services.IcsService import CalendarEvent from logic.tile.Tile import Tile @@ -23,10 +24,17 @@ class GarbageContainerScheduleTile(Tile): EXAMPLE_SETTINGS = { "path": "path/to/my/calendar.ics", "garbageType": "Papier" or "Gelbe Säcke" or "Bioabfall" or "Restabfall", + "notificationViaPushbullet": { + "enable": False, + "daysBeforeEvent": 1, + "hour": 10, + "pushbulletToken": None + } } def __init__(self, uniqueName: str, settings: Dict, intervalInSeconds: int): super().__init__(uniqueName, settings, intervalInSeconds) + self._lastNotificationDate = None def fetch(self, pageName: str) -> Dict: icsService = ServiceManager.get_instance().get_service_by_type_name('IcsService') @@ -43,28 +51,64 @@ class GarbageContainerScheduleTile(Tile): if nextEvent: nextEventDate = nextEvent.start if isinstance(nextEventDate, datetime): - remainingDays = nextEventDate - datetime.now() + remainingDays = nextEventDate - self._get_current_date_time() else: - remainingDays = nextEventDate - datetime.now().date() + remainingDays = nextEventDate - self._get_current_date_time().date() remainingDays = remainingDays.days nextEventDate = format_date(nextEventDate, self.DATE_FORMAT, 'de') iconName = self.ICON_BY_GARBAGE_TYPE[self._settings['garbageType']] + self._send_notification(remainingDays, nextEventDate) + return { 'nextEventDate': nextEventDate, 'iconFileName': f'{iconName}.png', 'remainingDays': remainingDays } + def _get_current_date_time(self) -> datetime: + return datetime.now() + def __find_next_date(self, events: List[CalendarEvent]) -> CalendarEvent or None: - now = datetime.now().date() + now = self._get_current_date_time().date() for event in events: if event.start < now: continue return event return None + def _send_notification(self, remainingDays: int, nextEventDate: str): + notificationSettings = self._settings['notificationViaPushbullet'] + if not notificationSettings['enable']: + self._lastNotificationDate = None + return + + if remainingDays != notificationSettings['daysBeforeEvent']: + self._lastNotificationDate = None + return + + now = self._get_current_date_time() + if now.hour < notificationSettings['hour']: + self._lastNotificationDate = None + return + + if self._is_already_notified(now): + self._lastNotificationDate = None + return + + self._lastNotificationDate = now.date() + title = 'DashboardLeaf - Garbage Schedule Notification' + description = f'"{self._settings["garbageType"]}" will be collected in {remainingDays} days ({nextEventDate})' + + Helpers.send_notification_via_pushbullet(notificationSettings['pushbulletToken'], title, description) + + def _is_already_notified(self, now: datetime) -> bool: + if self._lastNotificationDate is None: + return False + + return self._lastNotificationDate == now.date() + def render(self, data: Dict) -> str: return Tile.render_template(os.path.dirname(__file__), __class__.__name__, data=data) diff --git a/test/logic/tile/tiles/TestGarbageContainerScheduleTile.py b/test/logic/tile/tiles/TestGarbageContainerScheduleTile.py new file mode 100644 index 0000000..48821d8 --- /dev/null +++ b/test/logic/tile/tiles/TestGarbageContainerScheduleTile.py @@ -0,0 +1,87 @@ +from datetime import datetime +from unittest import mock + +from logic import Helpers +from logic.tile.tiles.GarbageContainerScheduleTile import GarbageContainerScheduleTile + + +def example_settings(enableNotifications): + return { + "path": None, + "garbageType": "Papier" or "Gelbe Säcke" or "Bioabfall" or "Restabfall", + "notificationViaPushbullet": { + "enable": enableNotifications, + "daysBeforeEvent": 1, + "hour": 10, + "pushbulletToken": "myToken" + } + } + + +class TestIsAlreadyNotified: + def test_not_notified_should_return_false(self): + tile = GarbageContainerScheduleTile('myGarbageScheduleTile', example_settings(False), 10) + assert tile._is_already_notified(datetime.now()) is False + + def test_already_notified_but_not_today_should_return_false(self): + tile = GarbageContainerScheduleTile('myGarbageScheduleTile', example_settings(False), 10) + + tile.__lastNotificationDate = datetime(year=2021, month=3, day=18).date() + currentDateTime = datetime(year=2021, month=3, day=21, hour=11, minute=35, second=0) + + assert tile._is_already_notified(currentDateTime) is False + + def test_already_notified_should_return_true(self): + tile = GarbageContainerScheduleTile('myGarbageScheduleTile', example_settings(False), 10) + + tile._lastNotificationDate = datetime(year=2021, month=3, day=21).date() + currentDateTime = datetime(year=2021, month=3, day=21, hour=11, minute=35, second=0) + + assert tile._is_already_notified(currentDateTime) is True + + +class TestSendNotification: + @mock.patch('logic.tile.tiles.GarbageContainerScheduleTile.Helpers') + def test_notification_disabled_should_do_nothing(self, helpersMock): + tile = GarbageContainerScheduleTile('myGarbageScheduleTile', example_settings(False), 10) + + tile._send_notification(1, '') + helpersMock.send_notification_via_pushbullet.assert_not_called() + + @mock.patch('logic.tile.tiles.GarbageContainerScheduleTile.Helpers') + def test_notification_enabled_reaming_days_greater_than_settings_should_do_nothing(self, helpersMock): + tile = GarbageContainerScheduleTile('myGarbageScheduleTile', example_settings(True), 10) + + tile._send_notification(3, '') + helpersMock.send_notification_via_pushbullet.assert_not_called() + + @mock.patch('logic.Helpers.requests') + def test_send_notification_after_settings_hour_should_call_pushbullet_api(self, requestsMock): + tile = GarbageContainerScheduleTile('myGarbageScheduleTile', example_settings(True), 10) + + requestsMock.post.return_value.status_code = 200 + with mock.patch.object(tile, '_get_current_date_time', wraps=tile._get_current_date_time) as currentDateTimeMock: + currentDateTimeMock.return_value = datetime(year=2021, month=3, day=21, hour=11, minute=35, second=0) + tile._send_notification(1, '') + + requestsMock.post.assert_called_once_with(Helpers.PUSHBULLET_PUSH_URL, data=mock.ANY, headers=mock.ANY) + + @mock.patch('logic.tile.tiles.GarbageContainerScheduleTile.Helpers') + def test_send_notification_before_settings_hour_should_do_nothing(self, helpersMock): + tile = GarbageContainerScheduleTile('myGarbageScheduleTile', example_settings(True), 10) + + with mock.patch.object(tile, '_get_current_date_time', wraps=tile._get_current_date_time) as currentDateTimeMock: + currentDateTimeMock.return_value = datetime(year=2021, month=3, day=21, hour=9, minute=35, second=0) + tile._send_notification(1, '') + + helpersMock.send_notification_via_pushbullet.assert_not_called() + + @mock.patch('logic.Helpers.requests') + def test_already_notified_should_skip_sending(self, requestsMock): + tile = GarbageContainerScheduleTile('myGarbageScheduleTile', example_settings(True), 10) + + requestsMock.post.return_value.status_code = 200 + tile._send_notification(1, '') + tile._send_notification(1, '') + + requestsMock.post.assert_called_once() -- GitLab