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.
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PKG-INFO +2 -2
- pyemailerajm-1.9.3/PyEmailerAJM/_version.py +1 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/logger.py +2 -1
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/continuous_monitor_base.py +70 -19
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/continuous_monitor.py +20 -14
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/PKG-INFO +2 -2
- pyemailerajm-1.9.3/tests/test_continuous_monitor_base.py +228 -0
- pyemailerajm-1.9.2/PyEmailerAJM/_version.py +0 -1
- pyemailerajm-1.9.2/tests/test_continuous_monitor_base.py +0 -104
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/LICENSE.txt +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/__init__.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/__init__.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/enums.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/errs.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/backend/the_sandman.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/__init__.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/__init__.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/continuous_colorizer.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/email_state.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/snooze_tracking.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/continuous_monitor_alert_send.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/msg/__init__.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/msg/alert_messages.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/msg/factory.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/msg/msg.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/py_emailer_ajm.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/searchers/__init__.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/searchers/factory.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/searchers/searchers.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/SOURCES.txt +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/dependency_links.txt +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/requires.txt +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM.egg-info/top_level.txt +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/README.md +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/setup.cfg +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/setup.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_PyEmailerAJM.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_email_signature.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_logger.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_msg_properties.py +0 -0
- {pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/tests/test_searcher_factory.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
{pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/continuous_monitor.py
RENAMED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/email_state.py
RENAMED
|
File without changes
|
{pyemailerajm-1.9.2 → pyemailerajm-1.9.3}/PyEmailerAJM/continuous_monitor/backend/snooze_tracking.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|