def test_notification_api_fail_5xx(self, now_mock, requests_post_mock): """ Notifications: Test API failure for notify() - HTTP 5xx """ now_mock.return_value = timezone.make_aware( timezone.datetime(2016, 11, 17, hour=0, minute=5)) requests_post_mock.return_value = mock.MagicMock(status_code=503, text='Server Error') notification_settings = NotificationSetting.get_solo() notification_settings.notification_service = NotificationSetting.NOTIFICATION_PROWL notification_settings.prowl_api_key = self.API_KEY notification_settings.next_notification = timezone.now() notification_settings.save() # When having no data, this should NOT raise an exception. if not self.fixtures: return dsmr_notification.services.notify() with self.assertRaises(AssertionError): dsmr_notification.services.notify() # HTTP 5xx should delay next call. notification_settings = NotificationSetting.get_solo() self.assertIsNotNone(notification_settings.notification_service) self.assertIsNotNone(notification_settings.prowl_api_key) self.assertGreater(notification_settings.next_notification, timezone.now())
def test_notifications(self, now_mock, requests_post_mock): """ Notifications: Test notify() (actual notification sender)""" now_mock.return_value = timezone.make_aware( timezone.datetime(2016, 11, 18, hour=0, minute=5)) requests_post_mock.return_value = mock.MagicMock(status_code=200, text='OK') notification_settings = NotificationSetting.get_solo() self.assertIsNone(notification_settings.next_notification) self.assertFalse(requests_post_mock.called) notification_settings.notification_service = NotificationSetting.NOTIFICATION_PROWL notification_settings.prowl_api_key = self.API_KEY notification_settings.next_notification = timezone.now() notification_settings.save() dsmr_notification.services.notify() if self.fixtures: self.assertTrue(requests_post_mock.called) else: return self.assertFalse(requests_post_mock.called) # Next notification should be pushed. self.assertGreater(NotificationSetting.get_solo().next_notification, timezone.now())
def test_notifications(self, now_mock, requests_post_mock): """ Notifications: Test notify() (actual notification sender)""" now_mock.return_value = timezone.make_aware( timezone.datetime(2016, 11, 17, hour=0, minute=5)) requests_post_mock.return_value = mock.MagicMock(status_code=200, text='OK') notification_settings = NotificationSetting.get_solo() self.assertIsNone(notification_settings.next_notification) self.assertFalse(requests_post_mock.called) notification_settings.notification_service = NotificationSetting.NOTIFICATION_PROWL notification_settings.prowl_api_key = self.API_KEY notification_settings.next_notification = timezone.now() notification_settings.save() dsmr_notification.services.notify() if self.fixtures: self.assertTrue(requests_post_mock.called) else: return self.assertFalse(requests_post_mock.called) # Make sure the expected message is created. yesterday = (timezone.localtime(timezone.now()) - timezone.timedelta(hours=24)).date() stats = DayStatistics.objects.get(day=yesterday) api_msg = dsmr_notification.services.create_consumption_message( yesterday, stats) self.assertTrue(yesterday.strftime("%d-%m-%Y") in api_msg) # Next notification should be pushed. self.assertGreater(NotificationSetting.get_solo().next_notification, timezone.now())
def test_notify_pre_check_dummy_message(self, now_mock, send_notification_mock): """ Notifications: Test notify_pre_check()'s output when service is set """ now_mock.return_value = timezone.make_aware( timezone.datetime(2018, 1, 1, 0, 0, 0)) # Should fail because we haven't set a service self.assertFalse(dsmr_notification.services.notify_pre_check()) notification_settings = NotificationSetting.get_solo() notification_settings.notification_service = NotificationSetting.NOTIFICATION_PROWL notification_settings.save() # Should be okay now, with dummy message being sent. self.assertFalse(send_notification_mock.called) self.assertIsNone(NotificationSetting.get_solo().next_notification) self.assertTrue( dsmr_notification.services.notify_pre_check()) # Execution self.assertTrue(send_notification_mock.called) self.assertIsNotNone(NotificationSetting.get_solo().next_notification) # 'next_notification' is no longer empty, so we should run the normal flow now. # First we verify the next_notification check. now_mock.return_value = now_mock.return_value - timezone.timedelta( minutes=1) self.assertFalse(dsmr_notification.services.notify_pre_check()) # And finally, the flow we were used to. now_mock.return_value = now_mock.return_value + timezone.timedelta( minutes=5) self.assertTrue(dsmr_notification.services.notify_pre_check())
def test_set_next_notification_date_for_dst_change(self, now_mock): """ Notifications: Test if next notification date is set due to DST change """ now_mock.return_value = timezone.make_aware(timezone.datetime(2018, 10, 27, 2, 0, 0)) # Do NOT change hour. now = timezone.localtime(timezone.now()) NotificationSetting.objects.update(next_notification=now) # This used to trigger a AmbiguousTimeError. dsmr_notification.services.set_next_notification() notification_settings = NotificationSetting.get_solo() next_notification = timezone.localtime(notification_settings.next_notification) expected = timezone.datetime(2018, 10, 28, 6, 0, 0) expected = timezone.localtime(timezone.make_aware(expected, pytz.timezone(settings.TIME_ZONE), is_dst=True)) print(next_notification, expected) self.assertEqual(next_notification, expected) # Check other way around as well, in March. now_mock.return_value = timezone.make_aware(timezone.datetime(2019, 3, 30, 1, 0, 0)) # Do NOT change hour. now = timezone.localtime(timezone.now()) NotificationSetting.objects.update(next_notification=now) dsmr_notification.services.set_next_notification() notification_settings = NotificationSetting.get_solo() next_notification = timezone.localtime(notification_settings.next_notification) expected = timezone.datetime(2019, 3, 31, 6, 0, 0) expected = timezone.localtime(timezone.make_aware(expected, pytz.timezone(settings.TIME_ZONE), is_dst=True)) self.assertEqual(next_notification, expected)
def test_notifications(self, now_mock, requests_post_mock): """ Notifications: Test notify() (actual notification sender)""" now_mock.return_value = timezone.make_aware( timezone.datetime(2016, 11, 17, hour=0, minute=5)) requests_post_mock.return_value = mock.MagicMock(status_code=200, text='OK') settings = NotificationSetting.get_solo() self.assertFalse(settings.send_notification) self.assertIsNone(settings.next_notification) self.assertFalse(requests_post_mock.called) settings.send_notification = True settings.notification_service = NotificationSetting.NOTIFICATION_NMA settings.api_key = 'es7sh2d-DSMR-Reader-Rulez-iweu732' settings.next_notification = timezone.localtime(timezone.now()) settings.save() dsmr_notification.services.notify() settings = NotificationSetting.get_solo() if self.fixtures: self.assertTrue(requests_post_mock.called) else: return self.assertFalse(requests_post_mock.called) nma_url = dsmr_notification.services.get_notification_api_url(settings) yesterday = (timezone.localtime(timezone.now()) - timezone.timedelta(hours=24)).date() stats = DayStatistics.objects.get(day=yesterday) api_msg = dsmr_notification.services.create_notification_message( yesterday, stats) self.assertTrue(yesterday.strftime("%d-%m-%Y") in api_msg) # Dissect call requests_post_mock.assert_called_once_with( nma_url, { 'apikey': settings.api_key, 'priority': dsmr_notification.services.get_notification_priority(), 'application': dsmr_notification.services.get_notification_sender_name(), 'event': dsmr_notification.services.get_notification_event_name(), 'description': api_msg }) tomorrow = (timezone.localtime(timezone.now()) + timezone.timedelta(hours=24)).date() self.assertEqual(settings.next_notification, tomorrow)
def check_status(): """ Checks the status of the application. """ status_settings = StatusNotificationSetting.get_solo() notification_settings = NotificationSetting.get_solo() if notification_settings.notification_service is None or \ not dsmr_backend.services.is_timestamp_passed(timestamp=status_settings.next_check): return if not DsmrReading.objects.exists(): return StatusNotificationSetting.objects.update( next_check=timezone.now() + timezone.timedelta(minutes=5)) # Check for recent data. has_recent_reading = DsmrReading.objects.filter( timestamp__gt=timezone.now() - timezone.timedelta(minutes=settings. DSMRREADER_STATUS_READING_OFFSET_MINUTES)).exists() if has_recent_reading: return StatusNotificationSetting.objects.update( next_check=timezone.now() + timezone.timedelta(minutes=5)) # Alert! logger.debug(' - Sending notification about datalogger lagging behind...') send_notification( str( _('It has been over {} hour(s) since the last reading received. Please check your datalogger.' )), str(_('Datalogger check'))) StatusNotificationSetting.objects.update( next_check=timezone.now() + timezone.timedelta( hours=settings.DSMRREADER_STATUS_NOTIFICATION_COOLDOWN_HOURS))
def notify(): """ Sends notifications about daily energy usage """ settings = NotificationSetting.get_solo() if not should_notify(settings): return # Just post the latest reading of the day before. today = timezone.localtime(timezone.now()) midnight = timezone.make_aware( timezone.datetime( year=today.year, month=today.month, day=today.day, hour=0, )) try: notification_api_url = get_notification_api_url(settings) except KeyError: raise AssertionError('Could not determine notification API url!') try: stats = DayStatistics.objects.get(day=(midnight - timezone.timedelta(hours=1))) except DayStatistics.DoesNotExist: return False # Try again in a next run # For backend logging in Supervisor. print(' - Creating new notification containing daily usage.') message = create_notification_message(midnight, stats) send_notification(notification_api_url, settings.api_key, message) set_next_notification(settings, today)
def test_check_status(self, now_mock, requests_post_mock): """ Check whether downtime of the datalogger triggers notifications. """ now_mock.return_value = timezone.make_aware( timezone.datetime(2018, 1, 1, hour=12)) requests_post_mock.return_value = mock.MagicMock(status_code=200, text='OK') StatusNotificationSetting.get_solo() notification_settings = NotificationSetting.get_solo() notification_settings.notification_service = NotificationSetting.NOTIFICATION_PROWL notification_settings.prowl_api_key = self.API_KEY notification_settings.save() # Schedule ahead. StatusNotificationSetting.objects.update(next_check=timezone.now() + timezone.timedelta(minutes=1)) dsmr_notification.services.check_status() # No data. StatusNotificationSetting.objects.update(next_check=timezone.now()) DsmrReading.objects.all().delete() dsmr_notification.services.check_status() self.assertGreater(StatusNotificationSetting.get_solo().next_check, timezone.now()) # Recent data. StatusNotificationSetting.objects.update(next_check=timezone.now()) DsmrReading.objects.create( timestamp=timezone.now() - timezone.timedelta(minutes=45), electricity_delivered_1=1, electricity_returned_1=2, electricity_delivered_2=3, electricity_returned_2=4, electricity_currently_delivered=5, electricity_currently_returned=6, ) dsmr_notification.services.check_status() self.assertGreater(StatusNotificationSetting.get_solo().next_check, timezone.now()) self.assertFalse(requests_post_mock.called) # Data from a while ago. StatusNotificationSetting.objects.update(next_check=timezone.now()) DsmrReading.objects.all().delete() DsmrReading.objects.create( timestamp=timezone.now() - timezone.timedelta(hours=24), electricity_delivered_1=1, electricity_returned_1=2, electricity_delivered_2=3, electricity_returned_2=4, electricity_currently_delivered=5, electricity_currently_returned=6, ) StatusNotificationSetting.objects.update(next_check=timezone.now()) self.assertFalse(requests_post_mock.called) dsmr_notification.services.check_status() self.assertTrue(requests_post_mock.called)
def test_notify_pre_check_skip(self, notify_pre_check_mock): """ Notifications: Test whether notify_pre_check() skips current day. """ notify_pre_check_mock.return_value = False notification_settings = NotificationSetting.get_solo() self.assertIsNone(notification_settings.notification_service) self.assertFalse(dsmr_notification.services.notify())
def send_notification(message, title): """ Sends notification using the preferred service """ notification_settings = NotificationSetting.get_solo() DATA_FORMAT = { NotificationSetting.NOTIFICATION_PUSHOVER: { 'url': NotificationSetting.PUSHOVER_API_URL, 'data': { 'token': notification_settings.pushover_api_key, 'user': notification_settings.pushover_user_key, 'priority': '-1', 'title': title, 'message': message } }, NotificationSetting.NOTIFICATION_PROWL: { 'url': NotificationSetting.PROWL_API_URL, 'data': { 'apikey': notification_settings.prowl_api_key, 'priority': '-2', 'application': 'DSMR-Reader', 'event': title, 'description': message } }, } response = requests.post( **DATA_FORMAT[notification_settings.notification_service]) if response.status_code == 200: return # Invalid request, do not retry. if str(response.status_code).startswith('4'): logger.error( ' - Notification API returned client error, wiping settings...') NotificationSetting.objects.update(notification_service=None, pushover_api_key=None, pushover_user_key=None, prowl_api_key=None, next_notification=None) Notification.objects.create( message='Notification API error, settings are reset. Error: {}'. format(response.text), redirect_to='admin:dsmr_notification_notificationsetting_changelist' ) # Server error, delay a bit. elif str(response.status_code).startswith('5'): logger.warning( ' - Notification API returned server error, retrying later...') NotificationSetting.objects.update(next_notification=timezone.now() + timezone.timedelta(minutes=5)) raise AssertionError('Notify API call failed: {0} (HTTP {1})'.format( response.text, response.status_code))
def test_invalid_api_url(self): """ Notifications: Test if inappropriate services get caught """ settings = NotificationSetting.get_solo() settings.send_notification = True settings.notification_service = 'DSMR-Reader-Rulez' settings.api_key = 'es7sh2d-DSMR-Reader-Rulez-iweu732' settings.save() with self.assertRaises(AssertionError): dsmr_notification.services.notify()
def test_no_daystatistics(self): """ Notifications: Test no notification because of no stats""" DayStatistics.objects.all().delete() settings = NotificationSetting.get_solo() settings.send_notification = True settings.notification_service = NotificationSetting.NOTIFICATION_NMA settings.api_key = 'es7sh2d-DSMR-Reader-Rulez-iweu732' settings.save() self.assertFalse(dsmr_notification.services.notify())
def test_set_next_notification_date(self, now_mock): """ Notifications: Test if next notification date is set """ now_mock.return_value = timezone.make_aware( timezone.datetime(2016, 11, 16, 0, 0, 0)) now = timezone.localtime(timezone.now()) NotificationSetting.objects.update(next_notification=now) dsmr_notification.services.set_next_notification() notification_settings = NotificationSetting.get_solo() self.assertEqual( notification_settings.next_notification, timezone.make_aware(timezone.datetime(2016, 11, 17, 6, 0, 0)))
def test_notification_dummy_provider_signal(self, now_mock, send_robust_mock): if not self.fixtures: return now_mock.return_value = timezone.make_aware(timezone.datetime(2016, 11, 17, hour=0, minute=5)) notification_settings = NotificationSetting.get_solo() notification_settings.notification_service = NotificationSetting.NOTIFICATION_DUMMY notification_settings.next_notification = timezone.now() notification_settings.save() self.assertFalse(send_robust_mock.called) dsmr_notification.services.notify() self.assertTrue(send_robust_mock.called)
def test_set_next_notification_date(self, now_mock): """ Notifications: Test if next notification date is set """ now_mock.return_value = timezone.make_aware( timezone.datetime(2016, 11, 16)) now = timezone.localtime(timezone.now()) tomorrow = (timezone.localtime(timezone.now()) + timezone.timedelta(hours=24)).date() settings = NotificationSetting.get_solo() settings.next_notification = now settings.save() dsmr_notification.services.set_next_notification(settings, now) self.assertEqual(settings.next_notification, tomorrow)
def test_notification_api_fail_other(self, now_mock, requests_post_mock): """ Notifications: Test API failure for notify() - HTTP xxx """ now_mock.return_value = timezone.make_aware(timezone.datetime(2016, 11, 17, hour=0, minute=5)) requests_post_mock.return_value = mock.MagicMock(status_code=300, text='xxxxx') # Just for code coverage. notification_settings = NotificationSetting.get_solo() notification_settings.notification_service = NotificationSetting.NOTIFICATION_PROWL notification_settings.prowl_api_key = self.API_KEY notification_settings.next_notification = timezone.now() notification_settings.save() # When having no data, this should NOT raise an exception. if not self.fixtures: return dsmr_notification.services.notify() with self.assertRaises(AssertionError): dsmr_notification.services.notify()
def get_context_data(self, **kwargs): context_data = super(Configuration, self).get_context_data(**kwargs) # 20+ queries, we should cache this at some point. context_data.update( dict( api_settings=APISettings.get_solo(), backend_settings=BackendSettings.get_solo(), backup_settings=BackupSettings.get_solo(), consumption_settings=ConsumptionSettings.get_solo(), datalogger_settings=DataloggerSettings.get_solo(), dropbox_settings=DropboxSettings.get_solo(), email_settings=EmailSettings.get_solo(), frontend_settings=FrontendSettings.get_solo(), mindergas_settings=MinderGasSettings.get_solo(), mqtt_broker_settings=MQTTBrokerSettings.get_solo(), mqtt_jsondaytotals_settings=JSONDayTotalsMQTTSettings.get_solo( ), mqtt_splittopicdaytotals_settings= SplitTopicDayTotalsMQTTSettings.get_solo(), mqtt_jsoncurrentperiodtotals_settings= JSONCurrentPeriodTotalsMQTTSettings.get_solo(), mqtt_splittopiccurrentperiodtotals_settings= SplitTopicCurrentPeriodTotalsMQTTSettings.get_solo(), mqtt_jsongasconsumption_settings=JSONGasConsumptionMQTTSettings .get_solo(), mqtt_splittopicgasconsumption_settings= SplitTopicGasConsumptionMQTTSettings.get_solo(), mqtt_splittopicmeterstatistics_settings= SplitTopicMeterStatisticsMQTTSettings.get_solo(), mqtt_jsontelegram_settings=JSONTelegramMQTTSettings.get_solo(), mqtt_rawtelegram_settings=RawTelegramMQTTSettings.get_solo(), mqtt_splittopictelegram_settings=SplitTopicTelegramMQTTSettings .get_solo(), notification_settings=NotificationSetting.get_solo(), pvoutput_api_settings=PVOutputAPISettings.get_solo(), pvoutput_addstatus_settings=PVOutputAddStatusSettings.get_solo( ), retention_settings=RetentionSettings.get_solo(), weather_settings=WeatherSettings.get_solo(), influxdb_settings=InfluxdbIntegrationSettings.get_solo(), )) return context_data
def test_should_notify_set(self): """ Notifications: Test should_notify()'s output when service is set """ settings = NotificationSetting.get_solo() settings.send_notification = True settings.notification_service = NotificationSetting.NOTIFICATION_NMA settings.save() self.assertTrue(settings.send_notification) # Should fail because we haven't set an API key self.assertFalse(dsmr_notification.services.should_notify(settings)) settings.api_key = 'es7sh2d-DSMR-Reader-Rulez-iweu732' settings.save() self.assertTrue(dsmr_notification.services.should_notify(settings)) settings.next_notification = None dsmr_notification.services.set_next_notification( settings, timezone.make_aware(timezone.datetime(2116, 11, 16))) self.assertFalse(dsmr_notification.services.should_notify(settings))
def notify_pre_check(): """ Checks whether we should notify """ notification_settings = NotificationSetting.get_solo() if notification_settings.notification_service is None: return False # Dummy message? if notification_settings.next_notification is None: send_notification(message='DSMR-reader notification test.', title='DSMR-reader Test Notification') NotificationSetting.objects.update(next_notification=timezone.now()) return True # Ready to go, but not time yet. if notification_settings.next_notification is not None and timezone.now( ) < notification_settings.next_notification: return False return True
def test_notification_api_fail(self, now_mock, requests_post_mock): """ Notifications: Test API failure for notify() """ now_mock.return_value = timezone.make_aware( timezone.datetime(2016, 11, 17, hour=0, minute=5)) requests_post_mock.return_value = mock.MagicMock(status_code=403, text='Forbidden') settings = NotificationSetting.get_solo() settings.send_notification = True settings.notification_service = NotificationSetting.NOTIFICATION_NMA settings.api_key = 'es7sh2d-DSMR-Reader-Rulez-iweu732' settings.next_notification = timezone.localtime(timezone.now()) settings.save() if self.fixtures: with self.assertRaises(AssertionError): dsmr_notification.services.notify() else: # When having no data, this should NOT raise an exception. return dsmr_notification.services.notify() with self.assertRaisesMessage( AssertionError, 'Notify API call failed: Forbidden (HTTP403)'): dsmr_notification.services.notify()
def test_should_notify_skip(self): """ Notifications: Test whether should_notify() skips current day. """ settings = NotificationSetting.get_solo() self.assertFalse(settings.send_notification) self.assertFalse(dsmr_notification.services.should_notify(settings))
def send_notification(message: str, title: str) -> None: """ Sends notification using the preferred service """ logger.debug(' - Preparing notification: %s | %s', title, message) notification_settings = NotificationSetting.get_solo() # Allow hooks (e.g. plugins) notification_sent.send_robust(None, title=title, message=message) # Plugins only require the hook above. if notification_settings.notification_service == NotificationSetting.NOTIFICATION_DUMMY \ or notification_settings.notification_service is None: logger.debug( ' - Notification service is dummy (or not set). Hook triggered, skipping notification.' ) return DATA_FORMAT = { NotificationSetting.NOTIFICATION_PUSHOVER: { 'url': NotificationSetting.PUSHOVER_API_URL, 'data': { 'token': notification_settings.pushover_api_key, 'user': notification_settings.pushover_user_key, 'priority': '-1', 'title': title, 'message': message } }, NotificationSetting.NOTIFICATION_PROWL: { 'url': NotificationSetting.PROWL_API_URL, 'data': { 'apikey': notification_settings.prowl_api_key, 'priority': '-2', 'application': 'DSMR-Reader', 'event': title, 'description': message } }, NotificationSetting.NOTIFICATION_TELEGRAM: { 'url': '{}{}/sendMessage'.format(NotificationSetting.TELEGRAM_API_URL, notification_settings.telegram_api_key), 'data': { 'chat_id': notification_settings.telegram_chat_id, 'disable_notification': 'true', 'text': message } }, } logger.debug(' - Sending notification') response = requests.post( timeout=settings.DSMRREADER_CLIENT_TIMEOUT, **DATA_FORMAT[notification_settings.notification_service]) if response.status_code == 200: logger.debug(' - Notification sent') return # Invalid request, do not retry. if str(response.status_code).startswith('4'): logger.error( ' - Notification API returned client error, wiping settings...') NotificationSetting.objects.update(notification_service=None, pushover_api_key=None, pushover_user_key=None, prowl_api_key=None, next_notification=None, telegram_api_key=None, telegram_chat_id=None) Notification.objects.create( message='Notification API error, settings are reset. Error: {}'. format(response.text), redirect_to='admin:dsmr_notification_notificationsetting_changelist' ) # Server error, delay a bit. elif str(response.status_code).startswith('5'): logger.warning( ' - Notification API returned server error, retrying later...') NotificationSetting.objects.update(next_notification=timezone.now() + timezone.timedelta(minutes=5)) raise AssertionError('Notify API call failed: {0} (HTTP {1})'.format( response.text, response.status_code))
def test_should_notify_default(self): """ Notifications: Test should_notify() default behaviour. """ settings = NotificationSetting.get_solo() self.assertFalse(settings.send_notification) self.assertFalse(dsmr_notification.services.should_notify(settings))
def test_notify_pre_check_default(self): """ Notifications: Test notify_pre_check() default behaviour. """ notification_settings = NotificationSetting.get_solo() self.assertIsNone(notification_settings.notification_service) self.assertFalse(dsmr_notification.services.notify_pre_check())
def setUp(self): NotificationSetting.get_solo()