fix(email): resolve SMTP authentication failure and improve configuration resilience
- Fixed a bug where missing 'id=0' in the 'cfg' table caused SMTP authentication to fail by defaulting to placeholder credentials. - Updated 'app/lib_email.py' to explicitly validate SMTP server and port settings before connecting, preventing crashes with 'please run connect() first'. - Added email fallback logic in 'app/methods/person_methods.py' to use 'user_email' or 'primary_email' if the primary contact email is missing. - Aligned 'app/config.py.default' with the production structure, explicitly re-adding 'SMTP' and 'FILES_PATH' dictionaries. - Added comprehensive unit tests in 'tests/test_email_configuration.py' to verify configuration handling.
This commit is contained in:
102
tests/test_email_configuration.py
Normal file
102
tests/test_email_configuration.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add project root to path
|
||||
sys.path.append(os.getcwd())
|
||||
|
||||
# --- Mocking Dependencies BEFORE Import ---
|
||||
|
||||
# 1. Mock html2text
|
||||
mock_html2text = MagicMock()
|
||||
mock_html2text.html2text.return_value = "Mock Text Content"
|
||||
sys.modules['html2text'] = mock_html2text
|
||||
|
||||
# 2. Mock app.config
|
||||
# We need to create a mock module and assign it to sys.modules['app.config']
|
||||
mock_config = MagicMock()
|
||||
# Create a Mock Settings object with an SMTP attribute
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.SMTP = {}
|
||||
mock_config.settings = mock_settings
|
||||
sys.modules['app.config'] = mock_config
|
||||
|
||||
# 3. Mock app.log with a functional decorator
|
||||
mock_log = MagicMock()
|
||||
|
||||
def simple_decorator(func):
|
||||
"""Pass-through decorator."""
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
mock_log.logger_reset = simple_decorator
|
||||
sys.modules['app.log'] = mock_log
|
||||
|
||||
# Now we can import the function to test
|
||||
from app.lib_email import send_email
|
||||
|
||||
class TestEmailConfiguration(unittest.TestCase):
|
||||
|
||||
@patch('app.lib_email.smtplib')
|
||||
def test_send_email_missing_server_config(self, mock_smtplib):
|
||||
"""Test that send_email returns False gracefully if server/port are missing."""
|
||||
print("\n--- Testing send_email with missing server/port ---")
|
||||
|
||||
# Setup the mock settings for this test
|
||||
sys.modules['app.config'].settings.SMTP = {
|
||||
'server': '',
|
||||
'port': '',
|
||||
'username': 'user',
|
||||
'password': 'password'
|
||||
}
|
||||
|
||||
result = send_email(
|
||||
from_email="test@example.com",
|
||||
to_email="recipient@example.com",
|
||||
subject="Test",
|
||||
body_html="<p>Body</p>",
|
||||
test=True
|
||||
)
|
||||
|
||||
print(f"Result: {result}")
|
||||
self.assertFalse(result)
|
||||
# Verify SMTP_SSL was NOT called
|
||||
mock_smtplib.SMTP_SSL.assert_not_called()
|
||||
|
||||
@patch('app.lib_email.smtplib')
|
||||
def test_send_email_valid_config(self, mock_smtplib):
|
||||
"""Test that send_email attempts to connect if config is valid."""
|
||||
print("\n--- Testing send_email with VALID config ---")
|
||||
|
||||
# Setup the mock settings for this test
|
||||
sys.modules['app.config'].settings.SMTP = {
|
||||
'server': 'smtp.example.com',
|
||||
'port': '465',
|
||||
'username': 'user',
|
||||
'password': 'password'
|
||||
}
|
||||
|
||||
# Mock successful connection context manager
|
||||
mock_server_instance = MagicMock()
|
||||
mock_smtplib.SMTP_SSL.return_value.__enter__.return_value = mock_server_instance
|
||||
|
||||
result = send_email(
|
||||
from_email="test@example.com",
|
||||
to_email="recipient@example.com",
|
||||
subject="Test",
|
||||
body_html="<p>Body</p>",
|
||||
test=False # Attempt real send (mocked)
|
||||
)
|
||||
|
||||
print(f"Result: {result}")
|
||||
self.assertTrue(result)
|
||||
|
||||
# Verify connection and login were called
|
||||
mock_smtplib.SMTP_SSL.assert_called_with('smtp.example.com', 465, context=unittest.mock.ANY)
|
||||
mock_server_instance.login.assert_called_with('user', 'password')
|
||||
mock_server_instance.send_message.assert_called()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
29
tests/verify_email.py
Normal file
29
tests/verify_email.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
|
||||
# Add current directory to path
|
||||
sys.path.append(os.getcwd())
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
try:
|
||||
from app.lib_email import send_email
|
||||
print("Successfully imported send_email.")
|
||||
|
||||
print("Running send_email in TEST mode...")
|
||||
result = send_email(
|
||||
from_email="test@example.com",
|
||||
to_email="test@example.com",
|
||||
subject="Test Email",
|
||||
body_html="<p>Test</p>",
|
||||
test=True
|
||||
)
|
||||
|
||||
print(f"Result: {result}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during email test: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
67
tests/verify_email_safe.py
Normal file
67
tests/verify_email_safe.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
# Add current directory to path
|
||||
sys.path.append(os.getcwd())
|
||||
|
||||
# Mock html2text before importing app.lib_email
|
||||
sys.modules['html2text'] = MagicMock()
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
from app.lib_email import send_email
|
||||
from app.config import settings
|
||||
|
||||
class TestEmail(unittest.TestCase):
|
||||
def test_send_email_missing_settings(self):
|
||||
print("\nTesting send_email with missing SMTP settings...")
|
||||
# Backup original settings
|
||||
original_smtp = getattr(settings, 'SMTP', {})
|
||||
|
||||
# Clear settings
|
||||
settings.SMTP = {}
|
||||
|
||||
result = send_email(
|
||||
from_email="test@example.com",
|
||||
to_email="test@example.com",
|
||||
subject="Test Email",
|
||||
body_html="<p>Test</p>",
|
||||
test=True
|
||||
)
|
||||
|
||||
# Restore settings
|
||||
settings.SMTP = original_smtp
|
||||
|
||||
print(f"Result (should be False): {result}")
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_send_email_invalid_port(self):
|
||||
print("\nTesting send_email with invalid port...")
|
||||
original_smtp = getattr(settings, 'SMTP', {})
|
||||
|
||||
settings.SMTP = {
|
||||
'server': 'smtp.example.com',
|
||||
'port': 'invalid',
|
||||
'username': 'user',
|
||||
'password': 'pass'
|
||||
}
|
||||
|
||||
result = send_email(
|
||||
from_email="test@example.com",
|
||||
to_email="test@example.com",
|
||||
subject="Test Email",
|
||||
body_html="<p>Test</p>",
|
||||
test=True
|
||||
)
|
||||
|
||||
settings.SMTP = original_smtp
|
||||
|
||||
print(f"Result (should be False): {result}")
|
||||
self.assertFalse(result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user