"""Notification logging must avoid stack inspection without changing delivery.""" import logging from pathlib import Path import sys from types import SimpleNamespace import unittest from unittest.mock import Mock, call, patch sys.path[:0] = [str(Path(__file__).resolve().parents[1] / 'apps'), str(Path(__file__).resolve().parents[1] / 'apps/ad_toolbox')] from appdaemon.adapi import ADAPI from notificationsmanager import NotificationsManager, Notification, SmartCondition class NotificationLoggingTests(unittest.TestCase): def setUp(self): self.api = ADAPI.__new__(ADAPI) self.api.AD = SimpleNamespace(config=SimpleNamespace(ascii_encode=False)) self.api.logger = Mock() self.sinks = {'main_log': Mock(), 'error_log': Mock()} self.api.get_user_log = Mock(side_effect=self.sinks.__getitem__) self.app = NotificationsManager.__new__(NotificationsManager) self.app.get_ad_api = Mock(return_value=self.api) self.app.args = {} self.app.call_service = Mock() self.app.run_in = Mock() self.app.stop_app = Mock() self.app._config_model = SimpleNamespace(name='notifications_manager') self.app.initialize() self.config = { 'message': {'title': 'Alarme', 'content': 'Activée', 'timeout': 10, 'data': {'tag': 'alarm'}}, 'recipients': {'phones': {'services': ['notify/one', 'notify/two']}} } self.notification = Notification(self.app, {'message': {}, 'recipients': {}}, 'alarm') self.notification.config = self.config def test_send_and_clear_keep_delivery_without_inspecting_stack(self): with patch('appdaemon.adapi.inspect.stack', side_effect=AssertionError('stack inspection')): self.notification.send_notification('phones') self.notification.clear_notification('phones', 'alarm') self.app.call_service.assert_has_calls([ call('notify/one', title='Alarme', message='Activée', data={'tag': 'alarm'}), call('notify/two', title='Alarme', message='Activée', data={'tag': 'alarm'}), call('notify/one', message='clear_notification', data={'tag': 'alarm'}), call('notify/two', message='clear_notification', data={'tag': 'alarm'}), ]) self.app.run_in.assert_called_once_with(self.notification.on_timeout_expired, 10, tag='alarm', recipient='phones') self.assertEqual(self.api.logger.log.call_count, 4) self.api.get_user_log.assert_not_called() def test_errors_use_app_logger_and_error_log_and_stop(self): with patch('appdaemon.adapi.inspect.stack', side_effect=AssertionError('stack inspection')): self.app.log_error('Échec', stop_app=True) for sink in (self.api.logger, self.sinks['error_log']): self.assertEqual(sink.log.call_args.args, (logging.ERROR, 'Échec')) self.sinks['main_log'].log.assert_not_called() self.app.stop_app.assert_called_once_with('notifications_manager') def test_smartcondition_fallback_uses_initialized_interface(self): self.config['recipients']['phones']['conditions'] = True self.app.args = {'notifications': {'alarm': self.config}} real_class = SmartCondition.Evaluator def factory(api, *args, **kwargs): condition = real_class.__new__(real_class) condition._Evaluator__logger_interface = None condition._Evaluator__appdaemon_api = api condition._Evaluator__log_info('Condition évaluée') return condition with patch('appdaemon.adapi.inspect.stack', side_effect=AssertionError('stack inspection')): with patch.object(SmartCondition, 'Evaluator', side_effect=factory): self.app.initialize() self.assertEqual(self.api.logger.log.call_args.args, (logging.INFO, 'Condition évaluée')) if __name__ == '__main__': unittest.main()