PyEmailerAJM 1.9.2__tar.gz → 1.9.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PKG-INFO +2 -2
  2. pyemailerajm-1.9.3/PyEmailerAJM/_version.py +1 -0
  3. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/logger.py +2 -1
  4. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/continuous_monitor_base.py +70 -19
  5. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/continuous_monitor.py +20 -14
  6. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/PKG-INFO +2 -2
  7. pyemailerajm-1.9.3/tests/test_continuous_monitor_base.py +228 -0
  8. pyemailerajm-1.9.2/PyEmailerAJM/_version.py +0 -1
  9. pyemailerajm-1.9.2/tests/test_continuous_monitor_base.py +0 -104
  10. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/LICENSE.txt +0 -0
  11. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/__init__.py +0 -0
  12. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/__init__.py +0 -0
  13. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/enums.py +0 -0
  14. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/errs.py +0 -0
  15. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/the_sandman.py +0 -0
  16. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/__init__.py +0 -0
  17. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/__init__.py +0 -0
  18. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/continuous_colorizer.py +0 -0
  19. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/email_state.py +0 -0
  20. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/snooze_tracking.py +0 -0
  21. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/continuous_monitor_alert_send.py +0 -0
  22. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/msg/__init__.py +0 -0
  23. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/msg/alert_messages.py +0 -0
  24. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/msg/factory.py +0 -0
  25. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/msg/msg.py +0 -0
  26. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/py_emailer_ajm.py +0 -0
  27. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/searchers/__init__.py +0 -0
  28. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/searchers/factory.py +0 -0
  29. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/searchers/searchers.py +0 -0
  30. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/SOURCES.txt +0 -0
  31. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/dependency_links.txt +0 -0
  32. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/requires.txt +0 -0
  33. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/top_level.txt +0 -0
  34. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/README.md +0 -0
  35. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/setup.cfg +0 -0
  36. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/setup.py +0 -0
  37. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_PyEmailerAJM.py +0 -0
  38. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_email_signature.py +0 -0
  39. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_logger.py +0 -0
  40. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_msg_properties.py +0 -0
  41. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_searcher_factory.py +0 -0
  42. {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_snooze_tracking.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyEmailerAJM
3
- Version: 1.9.2
3
+ Version: 1.9.3
4
4
  Summary: Allows for automating sending Email with the Outlook Desktop client. Future releases will add more client support
5
5
  Home-page: https://github.com/amcsparron2793-Water/PyEmailer
6
- Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.9.2.tar.gz
6
+ Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.9.3.tar.gz
7
7
  Author: Amcsparron
8
8
  Author-email: amcsparron@albanyny.gov
9
9
  License: MIT License
@@ -0,0 +1 @@
1
+ __version__ = '1.9.3'
@@ -1,7 +1,8 @@
1
1
  from logging import Filter, DEBUG, ERROR, Handler, FileHandler, StreamHandler, Logger, WARNING
2
2
  from typing import Union
3
3
 
4
- from EasyLoggerAJM import EasyLogger, OutlookEmailHandler, StreamHandlerIgnoreExecInfo
4
+ from EasyLoggerAJM import EasyLogger
5
+ from EasyLoggerAJM.logger_parts import OutlookEmailHandler, StreamHandlerIgnoreExecInfo
5
6
  from PyEmailerAJM.msg import Msg
6
7
 
7
8
 
@@ -75,13 +75,50 @@ class ContinuousMonitorBase(PyEmailer, EmailState):
75
75
  continue
76
76
  raise ValueError(f"{c} must be a list of email addresses")
77
77
 
78
+ def _normalize_logger(self, **kwargs):
79
+ # Normalize logger: if it's a factory, call it to get the instance
80
+ logger_arg = kwargs.pop('logger', self.logger)
81
+ if callable(logger_arg) and not hasattr(logger_arg, 'info'):
82
+ # It's a factory, not a logger instance
83
+ logger = logger_arg()
84
+ else:
85
+ logger = logger_arg
86
+ return logger
87
+
78
88
  def initialize_helper_classes(self, **kwargs):
79
- colorizer = ContinuousColorizer(logger=self.logger)
80
- snooze_tracker = SnoozeTracking(
81
- Path(kwargs.get('file_name', './snooze_tracker.json')),
82
- logger=self.logger,
83
- )
84
- sleep_timer = TheSandman(sleep_time_seconds=kwargs.get('sleep_time_seconds', None), logger=self.logger)
89
+ """
90
+ Initializes and returns instances of helper classes based on provided parameters.
91
+
92
+ This method is responsible for creating and configuring instances of helper
93
+ classes. It extracts configuration from the provided keyword arguments, uses
94
+ default class constructors when not overridden, and ensures logging and other
95
+ options are properly normalized and propagated.
96
+
97
+ :param kwargs: Configuration and initialization parameters for helper classes.
98
+ :type kwargs: dict
99
+ :return: A tuple containing instances of colorizer, snooze_tracker, and
100
+ sleep_timer in that order.
101
+ :rtype: tuple
102
+ """
103
+ logger = self._normalize_logger(**kwargs)
104
+ # Remove old logger from kwargs after normalization
105
+ kwargs.pop('logger', None)
106
+
107
+ # Extract helper class factories with defaults
108
+ colorizer_class = kwargs.pop('colorizer', ContinuousColorizer)
109
+ snooze_tracker_class = kwargs.pop('snooze_tracker', SnoozeTracking)
110
+ sleep_timer_class = kwargs.pop('sleep_timer', TheSandman)
111
+
112
+ # Initialize helper instances
113
+ colorizer = colorizer_class(logger=logger, **kwargs)
114
+
115
+ snooze_file_path = Path(kwargs.pop('file_name', './snooze_tracker.json'))
116
+ snooze_tracker = snooze_tracker_class(file_path=snooze_file_path, logger=logger, **kwargs)
117
+
118
+ sleep_time_seconds = kwargs.pop('sleep_time_seconds', None)
119
+ sleep_timer = sleep_timer_class(sleep_time_seconds=sleep_time_seconds,
120
+ logger=logger, **kwargs)
121
+
85
122
  return colorizer, snooze_tracker, sleep_timer
86
123
 
87
124
  def log_dev_mode_warnings(self):
@@ -92,24 +129,38 @@ class ContinuousMonitorBase(PyEmailer, EmailState):
92
129
  f" it will mock send emails but not actually send them to {self.__class__.ADMIN_EMAIL}"
93
130
  )
94
131
 
132
+ def _is_continuous_monitor_alert_send_subclass(self):
133
+ """Check if this instance is a ContinuousMonitorAlertSend subclass."""
134
+ is_named_match = type(self).__name__ == "ContinuousMonitorAlertSend"
135
+ is_dynamic_match = is_instance_of_dynamic(self, "__main__.ContinuousMonitorAlertSend")
136
+ return is_named_match or is_dynamic_match
137
+
138
+ def _should_skip_email_handler_init(self):
139
+ """Determine if email handler initialization should be skipped."""
140
+ if self.dev_mode:
141
+ self.logger.warning("email handler disabled for dev mode")
142
+ return True
143
+
144
+ if not self._is_continuous_monitor_alert_send_subclass():
145
+ self.logger.warning(
146
+ "email handler not initialized because this is not a "
147
+ "ContinuousMonitorAlertSend subclass"
148
+ )
149
+ return True
150
+
151
+ return False
152
+
95
153
  # Issue with PyEmailer 1.8.5 causes the base version to disable email handler
96
154
  # (issue with check for setup_email_handler attr) - below is a functional work around
97
- # TODO: allow the logger_class to be passed in as an arg
98
155
  def email_handler_init(self, **kwargs):
99
156
  logger_class = kwargs.get('logger_class', self.logger_class)
100
157
  try:
101
- if self.dev_mode:
102
- self.logger.warning("email handler disabled for dev mode")
103
- elif (not type(self).__name__ == "ContinuousMonitorAlertSend"
104
- and not is_instance_of_dynamic(self, "__main__.ContinuousMonitorAlertSend")):
105
- self.logger.warning(
106
- f"email handler not initialized because this is not a ContinuousMonitorAlertSend subclass"
107
- )
108
- else:
109
- logger_class.setup_email_handler(email_msg=self.email,
110
- logger_admins=self.__class__.ADMIN_EMAIL_LOGGER)
111
- self.email = self.initialize_new_email()
112
- self.logger.info("email handler initialized, initialized a new email object for use by monitor")
158
+ if self._should_skip_email_handler_init():
159
+ return
160
+ logger_class.setup_email_handler(email_msg=self.email,
161
+ logger_admins=self.__class__.ADMIN_EMAIL_LOGGER)
162
+ self.email = self.initialize_new_email()
163
+ self.logger.info("email handler initialized, initialized a new email object for use by monitor")
113
164
  except AttributeError as e:
114
165
  self.logger.error(f"email handler not initialized because {e}")
115
166
  pass
@@ -41,6 +41,25 @@ class ContinuousMonitor(ContinuousMonitorBase):
41
41
  def _postprocess_alert(self, alert_level=None, **kwargs):
42
42
  ...
43
43
 
44
+ def _process_no_alert(self, **kwargs):
45
+ no_alert_str = kwargs.get('no_alert_string',
46
+ self.__class__.NO_ALERTS_STR.format(read_folder=self.read_folder,
47
+ num_snoozed=self.num_snoozed_msgs))
48
+ self.logger.info(no_alert_str, print_msg=True)
49
+
50
+ def _classify_and_process(self, **kwargs):
51
+ if self.has_overdue:
52
+ self._print_and_postprocess(AlertTypes.OVERDUE)
53
+
54
+ elif self.has_warning:
55
+ self._print_and_postprocess(AlertTypes.WARNING)
56
+
57
+ elif self.has_critical_warning:
58
+ self._print_and_postprocess(AlertTypes.CRITICAL_WARNING)
59
+
60
+ else:
61
+ self._process_no_alert(**kwargs)
62
+
44
63
  # TODO: make no_alerts_string property so there is more flexibility with format
45
64
  def check_for_alerts(self, **kwargs):
46
65
  """
@@ -56,20 +75,7 @@ class ContinuousMonitor(ContinuousMonitorBase):
56
75
  self.logger.info(alert_check_string, print_msg=True)
57
76
  self.refresh_messages()
58
77
 
59
- if self.has_overdue:
60
- self._print_and_postprocess(AlertTypes.OVERDUE)
61
-
62
- elif self.has_warning:
63
- self._print_and_postprocess(AlertTypes.WARNING)
64
-
65
- elif self.has_critical_warning:
66
- self._print_and_postprocess(AlertTypes.CRITICAL_WARNING)
67
-
68
- else:
69
- no_alert_str = kwargs.get('no_alert_string',
70
- self.__class__.NO_ALERTS_STR.format(read_folder=self.read_folder,
71
- num_snoozed=self.num_snoozed_msgs))
72
- self.logger.info(no_alert_str, print_msg=True)
78
+ self._classify_and_process(**kwargs)
73
79
 
74
80
  self.snooze_tracker.snooze_msgs(self.all_messages)
75
81
 
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyEmailerAJM
3
- Version: 1.9.2
3
+ Version: 1.9.3
4
4
  Summary: Allows for automating sending Email with the Outlook Desktop client. Future releases will add more client support
5
5
  Home-page: https://github.com/amcsparron2793-Water/PyEmailer
6
- Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.9.2.tar.gz
6
+ Download-URL: https://github.com/amcsparron2793-Water/PyEmailer/archive/refs/tags/1.9.3.tar.gz
7
7
  Author: Amcsparron
8
8
  Author-email: amcsparron@albanyny.gov
9
9
  License: MIT License
@@ -0,0 +1,228 @@
1
+ import unittest
2
+ from unittest.mock import patch, MagicMock
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ from PyEmailerAJM.continuous_monitor.backend import ContinuousMonitorBase
7
+
8
+
9
+ class DummyMonitor(ContinuousMonitorBase):
10
+ def _postprocess_alert(self, alert_level=None, **kwargs):
11
+ # Mark that postprocess was called
12
+ self.postprocess_called = True
13
+
14
+
15
+ # Make DummyMonitor look like ContinuousMonitorAlertSend for the type check
16
+ class ContinuousMonitorAlertSend(ContinuousMonitorBase):
17
+ """Mock subclass that passes the type check in email_handler_init"""
18
+
19
+ def _postprocess_alert(self, alert_level=None, **kwargs):
20
+ self.postprocess_called = True
21
+
22
+
23
+ class DummyLoggerFactory:
24
+ """Callable logger factory with optional setup_email_handler capability"""
25
+
26
+ def __init__(self, with_email_handler=False):
27
+ self.with_email_handler = with_email_handler
28
+ self._last_kwargs = None
29
+
30
+ def __call__(self):
31
+ # Return a real Logger-like mock
32
+ mock_logger = MagicMock(spec=logging.Logger)
33
+ mock_logger.hasHandlers.return_value = False
34
+ mock_logger.handlers = []
35
+ return mock_logger
36
+
37
+ def setup_email_handler(self, **kwargs):
38
+ # record kwargs for assertion
39
+ self._last_kwargs = kwargs
40
+
41
+
42
+ # Dummy helper classes for initialize_helper_classes behavior checks
43
+ class DummyColorizer:
44
+ def __init__(self, logger=None, **kwargs):
45
+ self.logger = logger
46
+ self.kwargs = kwargs
47
+
48
+
49
+ class DummySnoozeTracker:
50
+ def __init__(self, file_path: Path, logger=None, **kwargs):
51
+ self.file_path = file_path
52
+ self.logger = logger
53
+ self.kwargs = kwargs
54
+ # default internal state for snooze json
55
+ self.json_loaded = []
56
+
57
+
58
+ class DummySleepTimer:
59
+ def __init__(self, sleep_time_seconds=None, logger=None, **kwargs):
60
+ self.sleep_time_seconds = sleep_time_seconds
61
+ self.logger = logger
62
+ self.kwargs = kwargs
63
+
64
+
65
+ class TestContinuousMonitorBase(unittest.TestCase):
66
+ def setUp(self) -> None:
67
+ # Avoid actual COM/Outlook initialization
68
+ self._init_email_patch = patch(
69
+ 'PyEmailerAJM.py_emailer_ajm.EmailerInitializer.initialize_email_item_app_and_namespace',
70
+ return_value=(None, None, MagicMock())
71
+ )
72
+ self._init_email_patch.start()
73
+
74
+ # Prevent EasyLogger from emitting during initialization
75
+ from EasyLoggerAJM.easy_logger import EasyLogger
76
+ self._post_handler_patcher = patch.object(EasyLogger, 'post_handler_setup', autospec=True)
77
+ self._post_handler_patcher.start()
78
+
79
+ # Provide logger factories used by tests
80
+ self.LoggerFactoryNoEmail = DummyLoggerFactory(with_email_handler=False)
81
+ self.LoggerFactoryWithEmail = DummyLoggerFactory(with_email_handler=True)
82
+
83
+ def tearDown(self) -> None:
84
+ self._post_handler_patcher.stop()
85
+ self._init_email_patch.stop()
86
+
87
+ def test_dev_mode_logs_and_disables_email_handler(self):
88
+ monitor = DummyMonitor(display_window=False, send_emails=False, dev_mode=True, logger=self.LoggerFactoryNoEmail)
89
+
90
+ # Expect dev mode warnings
91
+ logger = monitor.logger
92
+ calls = [c.args[0] for c in logger.warning.call_args_list]
93
+ self.assertTrue(any('DEV MODE ACTIVATED!' in msg for msg in calls))
94
+ self.assertTrue(any('email handler disabled for dev mode' in msg for msg in calls))
95
+
96
+ def test_skips_email_handler_when_not_alert_send_subclass(self):
97
+ monitor = DummyMonitor(display_window=False, send_emails=False, dev_mode=False,
98
+ logger=self.LoggerFactoryNoEmail)
99
+
100
+ # Should warn that email handler not initialized because not subclass
101
+ logger = monitor.logger
102
+ warning_calls = [c.args[0] for c in logger.warning.call_args_list]
103
+ self.assertTrue(
104
+ any('not initialized because this is not a ContinuousMonitorAlertSend' in msg for msg in warning_calls))
105
+
106
+ def test_print_and_postprocess_calls_postprocess_when_not_dev(self):
107
+ monitor = DummyMonitor(display_window=False, send_emails=False, dev_mode=False,
108
+ logger=self.LoggerFactoryNoEmail)
109
+ monitor.postprocess_called = False
110
+ monitor._print_and_postprocess(alert_level='INFO')
111
+ self.assertTrue(monitor.postprocess_called)
112
+
113
+ def test_print_and_postprocess_skips_postprocess_in_dev(self):
114
+ monitor = DummyMonitor(display_window=False, send_emails=False, dev_mode=True, logger=self.LoggerFactoryNoEmail)
115
+ monitor.postprocess_called = False
116
+ monitor._print_and_postprocess(alert_level='INFO')
117
+ self.assertFalse(monitor.postprocess_called)
118
+
119
+ def test_initializes_email_handler_when_factory_supports_it(self):
120
+ # Patch fresh email creation to a sentinel value and verify it gets set
121
+ with patch('PyEmailerAJM.py_emailer_ajm.EmailerInitializer.initialize_new_email', return_value='NEW_EMAIL'):
122
+ # Use ContinuousMonitorAlertSend instead of DummyMonitor to pass the type check
123
+ monitor = ContinuousMonitorAlertSend(display_window=False, send_emails=False, dev_mode=False,
124
+ logger=self.LoggerFactoryWithEmail)
125
+ # Logger factory should have been used to setup email handler with original email
126
+ self.assertIsNotNone(self.LoggerFactoryWithEmail._last_kwargs)
127
+ self.assertIn('email_msg', self.LoggerFactoryWithEmail._last_kwargs)
128
+ # After init, email should be replaced with NEW_EMAIL
129
+ self.assertEqual(monitor.email, 'NEW_EMAIL')
130
+
131
+ def test_email_handler_init_handles_attribute_error(self):
132
+ # When logger_class lacks setup_email_handler, should be caught and not raise
133
+ monitor = ContinuousMonitorAlertSend(display_window=False, send_emails=False, dev_mode=False,
134
+ logger=self.LoggerFactoryWithEmail)
135
+ # Use a dummy object without setup_email_handler to trigger AttributeError
136
+ monitor.email_handler_init(logger_class=object())
137
+ # Ensure an error was logged
138
+ error_calls = [c.args[0] for c in monitor.logger.error.call_args_list]
139
+ self.assertTrue(any('email handler not initialized because' in msg for msg in error_calls))
140
+
141
+ def test_is_continuous_monitor_alert_send_subclass(self):
142
+ a = DummyMonitor(display_window=False, send_emails=False, dev_mode=False, logger=self.LoggerFactoryNoEmail)
143
+ b = ContinuousMonitorAlertSend(display_window=False, send_emails=False, dev_mode=False,
144
+ logger=self.LoggerFactoryNoEmail)
145
+ self.assertFalse(a._is_continuous_monitor_alert_send_subclass())
146
+ self.assertTrue(b._is_continuous_monitor_alert_send_subclass())
147
+
148
+ def test_should_skip_email_handler_init_paths(self):
149
+ # dev mode skips
150
+ m1 = ContinuousMonitorAlertSend(display_window=False, send_emails=False, dev_mode=True,
151
+ logger=self.LoggerFactoryNoEmail)
152
+ self.assertTrue(m1._should_skip_email_handler_init())
153
+ # not subclass skips
154
+ m2 = DummyMonitor(display_window=False, send_emails=False, dev_mode=False, logger=self.LoggerFactoryNoEmail)
155
+ self.assertTrue(m2._should_skip_email_handler_init())
156
+ # valid subclass and not dev mode does not skip
157
+ m3 = ContinuousMonitorAlertSend(display_window=False, send_emails=False, dev_mode=False,
158
+ logger=self.LoggerFactoryNoEmail)
159
+ self.assertFalse(m3._should_skip_email_handler_init())
160
+
161
+ def test_normalize_logger_with_factory_and_instance(self):
162
+ # Using factory returns a logger instance
163
+ m = DummyMonitor(display_window=False, send_emails=False, dev_mode=False, logger=self.LoggerFactoryNoEmail)
164
+ l_from_factory = m._normalize_logger(logger=self.LoggerFactoryNoEmail)
165
+ self.assertTrue(hasattr(l_from_factory, 'info'))
166
+ # Using instance returns the same instance
167
+ logger_instance = MagicMock(spec=logging.Logger)
168
+ l_from_instance = m._normalize_logger(logger=logger_instance)
169
+ self.assertIs(l_from_instance, logger_instance)
170
+ # Not providing logger uses self.logger
171
+ l_default = m._normalize_logger()
172
+ self.assertIs(l_default, m.logger)
173
+
174
+ def test_initialize_helper_classes_and_num_snoozed_msgs(self):
175
+ m = DummyMonitor(display_window=False, send_emails=False, dev_mode=False, logger=self.LoggerFactoryNoEmail)
176
+ colorizer, snoozer, sleeper = m.initialize_helper_classes(
177
+ colorizer=DummyColorizer,
178
+ snooze_tracker=DummySnoozeTracker,
179
+ sleep_timer=DummySleepTimer,
180
+ file_name='my_snooze.json',
181
+ sleep_time_seconds=42,
182
+ extra_opt=123
183
+ )
184
+ # helper types
185
+ self.assertIsInstance(colorizer, DummyColorizer)
186
+ self.assertIsInstance(snoozer, DummySnoozeTracker)
187
+ self.assertIsInstance(sleeper, DummySleepTimer)
188
+ # logger propagated
189
+ self.assertIs(colorizer.logger, m.logger)
190
+ self.assertIs(snoozer.logger, m.logger)
191
+ self.assertIs(sleeper.logger, m.logger)
192
+ # file path and sleep time propagated and processed
193
+ self.assertTrue(str(snoozer.file_path).endswith('my_snooze.json'))
194
+ self.assertEqual(sleeper.sleep_time_seconds, 42)
195
+ # extra options should pass to colorizer and snoozer and sleeper
196
+ self.assertEqual(colorizer.kwargs.get('extra_opt'), 123)
197
+ self.assertEqual(snoozer.kwargs.get('extra_opt'), 123)
198
+ self.assertEqual(sleeper.kwargs.get('extra_opt'), 123)
199
+ # Verify num_snoozed_msgs reflects json_loaded length
200
+ m.snooze_tracker = snoozer
201
+ snoozer.json_loaded = [1, 2, 3]
202
+ self.assertEqual(m.num_snoozed_msgs, 3)
203
+ # When no json_loaded or not sized, expect 0
204
+ snoozer.json_loaded = None
205
+ self.assertEqual(m.num_snoozed_msgs, 0)
206
+
207
+ def test_check_for_class_attrs(self):
208
+ class GoodSub(ContinuousMonitorBase):
209
+ ADMIN_EMAIL = ['a@example.com']
210
+
211
+ def _postprocess_alert(self, alert_level=None, **kwargs):
212
+ pass
213
+
214
+ class BadSub(ContinuousMonitorBase):
215
+ ADMIN_EMAIL = []
216
+
217
+ def _postprocess_alert(self, alert_level=None, **kwargs):
218
+ pass
219
+
220
+ # Good should not raise
221
+ GoodSub.check_for_class_attrs(['ADMIN_EMAIL'])
222
+ # Bad should raise
223
+ with self.assertRaises(ValueError):
224
+ BadSub.check_for_class_attrs(['ADMIN_EMAIL'])
225
+
226
+
227
+ if __name__ == '__main__':
228
+ unittest.main()
@@ -1 +0,0 @@
1
- __version__ = '1.9.2'
@@ -1,104 +0,0 @@
1
- import unittest
2
- from unittest.mock import patch, MagicMock
3
- import logging
4
-
5
- from PyEmailerAJM.continuous_monitor.backend import ContinuousMonitorBase
6
-
7
-
8
- class DummyMonitor(ContinuousMonitorBase):
9
- def _postprocess_alert(self, alert_level=None, **kwargs):
10
- # Mark that postprocess was called
11
- self.postprocess_called = True
12
-
13
-
14
- # Make DummyMonitor look like ContinuousMonitorAlertSend for the type check
15
- class ContinuousMonitorAlertSend(ContinuousMonitorBase):
16
- """Mock subclass that passes the type check in email_handler_init"""
17
- def _postprocess_alert(self, alert_level=None, **kwargs):
18
- self.postprocess_called = True
19
-
20
-
21
- class DummyLoggerFactory:
22
- """Callable logger factory with optional setup_email_handler capability"""
23
- def __init__(self, with_email_handler=False):
24
- self.with_email_handler = with_email_handler
25
- self._last_kwargs = None
26
-
27
- def __call__(self):
28
- # Return a real Logger-like mock
29
- mock_logger = MagicMock(spec=logging.Logger)
30
- mock_logger.hasHandlers.return_value = False
31
- mock_logger.handlers = []
32
- return mock_logger
33
-
34
- def setup_email_handler(self, **kwargs):
35
- # record kwargs for assertion
36
- self._last_kwargs = kwargs
37
-
38
-
39
- class TestContinuousMonitorBase(unittest.TestCase):
40
- def setUp(self) -> None:
41
- # Avoid actual COM/Outlook initialization
42
- self._init_email_patch = patch(
43
- 'PyEmailerAJM.py_emailer_ajm.EmailerInitializer.initialize_email_item_app_and_namespace',
44
- return_value=(None, None, MagicMock())
45
- )
46
- self._init_email_patch.start()
47
-
48
- # Prevent EasyLogger from emitting during initialization
49
- from EasyLoggerAJM.easy_logger import EasyLogger
50
- self._post_handler_patcher = patch.object(EasyLogger, 'post_handler_setup', autospec=True)
51
- self._post_handler_patcher.start()
52
-
53
- # Provide logger factories used by tests
54
- self.LoggerFactoryNoEmail = DummyLoggerFactory(with_email_handler=False)
55
- self.LoggerFactoryWithEmail = DummyLoggerFactory(with_email_handler=True)
56
-
57
- def tearDown(self) -> None:
58
- self._post_handler_patcher.stop()
59
- self._init_email_patch.stop()
60
-
61
- def test_dev_mode_logs_and_disables_email_handler(self):
62
- monitor = DummyMonitor(display_window=False, send_emails=False, dev_mode=True, logger=self.LoggerFactoryNoEmail)
63
-
64
- # Expect dev mode warnings
65
- logger = monitor.logger
66
- calls = [c.args[0] for c in logger.warning.call_args_list]
67
- self.assertTrue(any('DEV MODE ACTIVATED!' in msg for msg in calls))
68
- self.assertTrue(any('email handler disabled for dev mode' in msg for msg in calls))
69
-
70
- def test_skips_email_handler_when_logger_factory_has_no_setup(self):
71
- monitor = DummyMonitor(display_window=False, send_emails=False, dev_mode=False, logger=self.LoggerFactoryNoEmail)
72
-
73
- # Should debug that email handler not initialized due to lack of capability
74
- logger = monitor.logger
75
- warning_calls = [c.args[0] for c in logger.warning.call_args_list]
76
- self.assertTrue(any('not initialized because this is not a ContinuousMonitorAlertSend' in msg for msg in warning_calls))
77
-
78
- def test_print_and_postprocess_calls_postprocess_when_not_dev(self):
79
- monitor = DummyMonitor(display_window=False, send_emails=False, dev_mode=False, logger=self.LoggerFactoryNoEmail)
80
- monitor.postprocess_called = False
81
- monitor._print_and_postprocess(alert_level='INFO')
82
- self.assertTrue(monitor.postprocess_called)
83
-
84
- def test_print_and_postprocess_skips_postprocess_in_dev(self):
85
- monitor = DummyMonitor(display_window=False, send_emails=False, dev_mode=True, logger=self.LoggerFactoryNoEmail)
86
- monitor.postprocess_called = False
87
- monitor._print_and_postprocess(alert_level='INFO')
88
- self.assertFalse(monitor.postprocess_called)
89
-
90
- def test_initializes_email_handler_when_factory_supports_it(self):
91
- # Patch fresh email creation to a sentinel value and verify it gets set
92
- with patch('PyEmailerAJM.py_emailer_ajm.EmailerInitializer.initialize_new_email', return_value='NEW_EMAIL'):
93
- # Use ContinuousMonitorAlertSend instead of DummyMonitor to pass the type check
94
- monitor = ContinuousMonitorAlertSend(display_window=False, send_emails=False, dev_mode=False,
95
- logger=self.LoggerFactoryWithEmail)
96
- # Logger factory should have been used to setup email handler with original email
97
- self.assertIsNotNone(self.LoggerFactoryWithEmail._last_kwargs)
98
- self.assertIn('email_msg', self.LoggerFactoryWithEmail._last_kwargs)
99
- # After init, email should be replaced with NEW_EMAIL
100
- self.assertEqual(monitor.email, 'NEW_EMAIL')
101
-
102
-
103
- if __name__ == '__main__':
104
- unittest.main()
File without changes
File without changes
File without changes
File without changes