Files
TimeTracker/tests/test_email.py
Dries Peeters a548928064 tests
2025-10-27 17:46:50 +01:00

323 lines
12 KiB
Python

"""
Tests for email functionality
"""
import pytest
from unittest.mock import patch, MagicMock
from flask import current_app
from app.utils.email import (
send_email,
check_email_configuration,
send_test_email,
init_mail
)
class TestEmailConfiguration:
"""Tests for email configuration"""
def test_init_mail(self, app):
"""Test email initialization"""
with app.app_context():
mail = init_mail(app)
assert mail is not None
assert 'MAIL_SERVER' in app.config
assert 'MAIL_PORT' in app.config
assert 'MAIL_DEFAULT_SENDER' in app.config
def test_email_config_status_not_configured(self, app):
"""Test email configuration status when not configured"""
with app.app_context():
# Reset mail server to simulate unconfigured state
app.config['MAIL_SERVER'] = 'localhost'
status = check_email_configuration()
assert status is not None
assert 'configured' in status
assert 'settings' in status
assert 'errors' in status
assert 'warnings' in status
assert status['configured'] is False
assert len(status['errors']) > 0
def test_email_config_status_configured(self, app):
"""Test email configuration status when properly configured"""
with app.app_context():
# Set up proper configuration
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USE_SSL'] = False
app.config['MAIL_USERNAME'] = 'test@example.com'
app.config['MAIL_PASSWORD'] = 'test_password'
app.config['MAIL_DEFAULT_SENDER'] = 'noreply@example.com'
status = check_email_configuration()
assert status is not None
assert status['configured'] is True
assert len(status['errors']) == 0
assert status['settings']['server'] == 'smtp.gmail.com'
assert status['settings']['port'] == 587
assert status['settings']['password_set'] is True
def test_email_config_warns_about_default_sender(self, app):
"""Test that configuration warns about default sender"""
with app.app_context():
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_DEFAULT_SENDER'] = 'noreply@timetracker.local'
status = check_email_configuration()
assert len(status['warnings']) > 0
assert any('Default sender' in w for w in status['warnings'])
def test_email_config_errors_on_both_tls_and_ssl(self, app):
"""Test that configuration errors when both TLS and SSL are enabled"""
with app.app_context():
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USE_SSL'] = True
status = check_email_configuration()
assert len(status['errors']) > 0
assert any('TLS and SSL' in e for e in status['errors'])
class TestSendEmail:
"""Tests for sending emails"""
@patch('app.utils.email.mail.send')
@patch('app.utils.email.Thread')
def test_send_email_success(self, mock_thread, mock_send, app):
"""Test sending email successfully"""
with app.app_context():
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
send_email(
subject='Test Subject',
recipients=['test@example.com'],
text_body='Test body',
html_body='<p>Test body</p>'
)
# Verify thread was started for async sending
assert mock_thread.called
def test_send_email_no_server(self, app):
"""Test sending email with no mail server configured"""
with app.app_context():
app.config['MAIL_SERVER'] = None
# This should not raise an exception, but log a warning and return early
send_email(
subject='Test Subject',
recipients=['test@example.com'],
text_body='Test body'
)
# The function should return without error
# (The warning is logged but we can't easily capture it in this context)
def test_send_email_no_recipients(self, app):
"""Test sending email with no recipients"""
with app.app_context():
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
# This should not raise an exception, but log a warning and return early
send_email(
subject='Test Subject',
recipients=[],
text_body='Test body'
)
# The function should return without error
# (The warning is logged but we can't easily capture it in this context)
@patch('app.utils.email.mail.send')
def test_send_test_email_success(self, mock_send, app):
"""Test sending test email successfully"""
with app.app_context():
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_DEFAULT_SENDER'] = 'test@example.com'
success, message = send_test_email('recipient@example.com', 'Test Sender')
assert success is True
assert 'successfully' in message.lower()
assert mock_send.called
def test_send_test_email_invalid_recipient(self, app):
"""Test sending test email with invalid recipient"""
with app.app_context():
success, message = send_test_email('invalid-email', 'Test Sender')
assert success is False
assert 'Invalid' in message
def test_send_test_email_no_server(self, app):
"""Test sending test email with no mail server"""
with app.app_context():
app.config['MAIL_SERVER'] = None
success, message = send_test_email('test@example.com', 'Test Sender')
assert success is False
assert 'not configured' in message
@patch('app.utils.email.mail.send')
def test_send_test_email_exception(self, mock_send, app):
"""Test sending test email with exception"""
with app.app_context():
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_DEFAULT_SENDER'] = 'test@example.com'
# Simulate exception
mock_send.side_effect = Exception('SMTP error')
success, message = send_test_email('test@example.com', 'Test Sender')
assert success is False
assert 'Failed' in message
class TestEmailIntegration:
"""Integration tests for email functionality"""
def check_email_configuration_in_app_context(self, app):
"""Test that email configuration is available in app context"""
with app.app_context():
assert hasattr(current_app, 'config')
assert 'MAIL_SERVER' in current_app.config
assert 'MAIL_PORT' in current_app.config
assert 'MAIL_USE_TLS' in current_app.config
assert 'MAIL_DEFAULT_SENDER' in current_app.config
def test_email_settings_from_environment(self, app, monkeypatch):
"""Test that email settings are loaded from environment"""
# Set environment variables
monkeypatch.setenv('MAIL_SERVER', 'smtp.test.com')
monkeypatch.setenv('MAIL_PORT', '465')
monkeypatch.setenv('MAIL_USE_SSL', 'true')
# Reinitialize mail with new environment
with app.app_context():
mail = init_mail(app)
assert app.config['MAIL_SERVER'] == 'smtp.test.com'
assert app.config['MAIL_PORT'] == 465
assert app.config['MAIL_USE_SSL'] is True
class TestDatabaseEmailConfiguration:
"""Tests for database-backed email configuration"""
def test_get_mail_config_when_disabled(self, app):
"""Test get_mail_config returns None when database config is disabled"""
with app.app_context():
from app.models import Settings
settings = Settings.get_settings()
settings.mail_enabled = False
settings.mail_server = 'smtp.test.com'
config = settings.get_mail_config()
assert config is None
def test_get_mail_config_when_enabled(self, app):
"""Test get_mail_config returns config when enabled"""
with app.app_context():
from app.models import Settings
settings = Settings.get_settings()
settings.mail_enabled = True
settings.mail_server = 'smtp.test.com'
settings.mail_port = 587
settings.mail_use_tls = True
settings.mail_use_ssl = False
settings.mail_username = 'test@example.com'
settings.mail_password = 'test_password'
settings.mail_default_sender = 'noreply@example.com'
config = settings.get_mail_config()
assert config is not None
assert config['MAIL_SERVER'] == 'smtp.test.com'
assert config['MAIL_PORT'] == 587
assert config['MAIL_USE_TLS'] is True
assert config['MAIL_USE_SSL'] is False
assert config['MAIL_USERNAME'] == 'test@example.com'
assert config['MAIL_PASSWORD'] == 'test_password'
assert config['MAIL_DEFAULT_SENDER'] == 'noreply@example.com'
def test_init_mail_uses_database_config(self, app):
"""Test that init_mail uses database settings when available"""
with app.app_context():
from app.models import Settings
from app.utils.email import init_mail
from app import db
settings = Settings.get_settings()
settings.mail_enabled = True
settings.mail_server = 'smtp.database.com'
settings.mail_port = 465
db.session.commit()
init_mail(app)
# Should use database settings
assert app.config['MAIL_SERVER'] == 'smtp.database.com'
assert app.config['MAIL_PORT'] == 465
def test_reload_mail_config(self, app):
"""Test reloading email configuration"""
with app.app_context():
from app.models import Settings
from app.utils.email import reload_mail_config
from app import db
# Set up database config
settings = Settings.get_settings()
settings.mail_enabled = True
settings.mail_server = 'smtp.reloaded.com'
db.session.commit()
# Reload configuration
success = reload_mail_config(app)
assert success is True
assert app.config['MAIL_SERVER'] == 'smtp.reloaded.com'
def test_check_email_configuration_shows_source(self, app):
"""Test that configuration status shows source (database or environment)"""
with app.app_context():
from app.models import Settings
from app.utils.email import check_email_configuration
from app import db
# Test with database config
settings = Settings.get_settings()
settings.mail_enabled = True
settings.mail_server = 'smtp.database.com'
db.session.commit()
status = check_email_configuration()
assert 'source' in status
assert status['source'] == 'database'
# Test with environment config
settings.mail_enabled = False
db.session.commit()
status = check_email_configuration()
assert status['source'] == 'environment'
# Fixtures
@pytest.fixture
def mock_mail_send():
"""Mock the mail.send method"""
with patch('app.utils.email.mail.send') as mock:
yield mock