Merge pull request #513 from christianlouis/copilot/fix-email-template-issue

fix(email): create missing email template and decouple delivery SMTP from auth SMTP
This commit is contained in:
Christian Krakau-Louis
2026-03-08 11:58:40 +01:00
committed by GitHub
14 changed files with 260 additions and 82 deletions
+14 -2
View File
@@ -197,14 +197,26 @@ OPENAI_MODEL=gpt-4o-mini
# AI_MODEL=gpt-4o # deployment name in Azure
# Azure Document Intelligence (OCR separate from AI provider above)
# **Email Settings**
# **Email Settings (shared SMTP password reset, verification, and system notifications)**
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USERNAME=docuelevate@example.com
EMAIL_PASSWORD=your_secure_email_password
EMAIL_USE_TLS=True
EMAIL_SENDER=DocuElevate System <docuelevate@example.com>
EMAIL_DEFAULT_RECIPIENT=recipient@example.com
# EMAIL_DEFAULT_RECIPIENT is not used for document delivery (see DEST_EMAIL_* below)
# **Email Destination Settings (dedicated SMTP for document delivery)**
# These settings are intentionally separate from the shared EMAIL_* settings above.
# Configuring EMAIL_HOST for password reset / notifications does NOT automatically
# enable the email destination you must set DEST_EMAIL_HOST to activate it.
DEST_EMAIL_HOST=smtp.example.com
DEST_EMAIL_PORT=587
DEST_EMAIL_USERNAME=docuelevate@example.com
DEST_EMAIL_PASSWORD=your_secure_email_password
DEST_EMAIL_USE_TLS=True
DEST_EMAIL_SENDER=DocuElevate Delivery <docuelevate@example.com>
DEST_EMAIL_DEFAULT_RECIPIENT=recipient@example.com
# **Watch Folder Ingestion**
# DocuElevate can automatically monitor directories (local, FTP, SFTP, and cloud providers) for new files.
+10 -1
View File
@@ -432,7 +432,7 @@ class Settings(BaseSettings):
# In development/testing, set to True to disable verification (not recommended)
sftp_disable_host_key_verification: bool = False # Default enforces host key verification
# Email settings
# Email settings (shared SMTP used for password reset, verification emails, etc.)
email_host: Optional[str] = None
email_port: Optional[int] = 587
email_username: Optional[str] = None
@@ -441,6 +441,15 @@ class Settings(BaseSettings):
email_sender: Optional[str] = None # From address, defaults to email_username if not set
email_default_recipient: Optional[str] = None
# Email destination settings (dedicated SMTP for document delivery decoupled from shared email above)
dest_email_host: Optional[str] = None
dest_email_port: Optional[int] = 587
dest_email_username: Optional[str] = None
dest_email_password: Optional[str] = None
dest_email_use_tls: bool = True
dest_email_sender: Optional[str] = None # From address for delivered documents
dest_email_default_recipient: Optional[str] = None # Fallback recipient for document delivery
# OneDrive settings
onedrive_client_id: Optional[str] = None
onedrive_client_secret: Optional[str] = None
+4 -1
View File
@@ -64,7 +64,10 @@ def _should_upload_to_sftp():
def _should_upload_to_email():
return bool(
settings.email_host and settings.email_username and settings.email_password and settings.email_default_recipient
settings.dest_email_host
and settings.dest_email_username
and settings.dest_email_password
and settings.dest_email_default_recipient
)
+15 -15
View File
@@ -125,11 +125,11 @@ def attach_logo(msg):
def _prepare_recipients(recipients):
"""Helper function to prepare email recipients list."""
if not recipients:
if not settings.email_default_recipient:
if not settings.dest_email_default_recipient:
error_msg = "No recipients specified and no default recipient configured"
logger.error(error_msg)
return None, error_msg
return [settings.email_default_recipient], None
return [settings.dest_email_default_recipient], None
elif isinstance(recipients, str):
return [recipients], None # Convert single email to list
return recipients, None
@@ -139,17 +139,17 @@ def _send_email_with_smtp(msg, filename, recipients):
"""Helper function to handle SMTP connection and sending."""
try:
# First try to resolve the hostname
socket.gethostbyname(settings.email_host)
socket.gethostbyname(settings.dest_email_host)
# Connect to the SMTP server
with smtplib.SMTP(settings.email_host, settings.email_port, timeout=30) as server:
with smtplib.SMTP(settings.dest_email_host, settings.dest_email_port, timeout=30) as server:
# Use TLS if specified
if settings.email_use_tls:
if settings.dest_email_use_tls:
server.starttls()
# Login if credentials are provided
if settings.email_username and settings.email_password:
server.login(settings.email_username, settings.email_password)
if settings.dest_email_username and settings.dest_email_password:
server.login(settings.dest_email_username, settings.dest_email_password)
# Send the email
server.send_message(msg)
@@ -157,11 +157,11 @@ def _send_email_with_smtp(msg, filename, recipients):
logger.info(f"Successfully sent {filename} via email to {', '.join(recipients)}")
return None
except socket.gaierror as e:
error_msg = f"Failed to resolve email host: {settings.email_host} - {str(e)}"
error_msg = f"Failed to resolve email host: {settings.dest_email_host} - {str(e)}"
logger.error(error_msg)
return {"status": "Failed", "reason": error_msg, "error": str(e)}
except (ConnectionRefusedError, TimeoutError) as e:
error_msg = f"Connection error to SMTP server {settings.email_host}:{settings.email_port} - {str(e)}"
error_msg = f"Connection error to SMTP server {settings.dest_email_host}:{settings.dest_email_port} - {str(e)}"
logger.error(error_msg)
return {"status": "Failed", "reason": error_msg, "error": str(e)}
@@ -205,17 +205,17 @@ def upload_to_email(
# Extract filename
filename = os.path.basename(file_path)
# Check if email settings are configured
if not settings.email_host:
error_msg = "Email host is not configured"
# Check if email destination settings are configured
if not settings.dest_email_host:
error_msg = "Email destination host is not configured (DEST_EMAIL_HOST)"
logger.error(f"[{task_id}] {error_msg}")
log_task_progress(task_id, "upload_to_email", "skipped", error_msg, file_id=file_id)
return {"status": "Skipped", "reason": error_msg}
# Log email configuration for debugging
logger.debug(
f"[{task_id}] Email config - Host: {settings.email_host}, Port: {settings.email_port}, "
f"Username: {settings.email_username}, TLS: {settings.email_use_tls}"
f"[{task_id}] Email destination config - Host: {settings.dest_email_host}, Port: {settings.dest_email_port}, "
f"Username: {settings.dest_email_username}, TLS: {settings.dest_email_use_tls}"
)
# Process recipients
@@ -236,7 +236,7 @@ def upload_to_email(
try:
# Create the email
msg = MIMEMultipart("related")
msg["From"] = settings.email_sender or settings.email_username
msg["From"] = settings.dest_email_sender or settings.dest_email_username
msg["To"] = ", ".join(recipients)
msg["Subject"] = subject
+63
View File
@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ filename }} DocuElevate</title>
<style>
body { margin: 0; padding: 0; background-color: #f4f4f5; font-family: Arial, Helvetica, sans-serif; }
.wrapper { max-width: 600px; margin: 32px auto; background: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,.08); overflow: hidden; }
.header { background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); padding: 32px 40px; text-align: center; }
.header img { max-height: 48px; }
.header h1 { color: #ffffff; font-size: 22px; margin: 16px 0 0; }
.body { padding: 32px 40px; }
.body p { color: #374151; font-size: 15px; line-height: 1.6; margin: 0 0 16px; }
.attachment-box { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 16px 20px; margin: 24px 0; }
.attachment-box .label { font-size: 11px; font-weight: bold; color: #6b7280; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 6px; }
.attachment-box .filename { color: #111827; font-size: 15px; font-weight: bold; word-break: break-all; }
.metadata-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 13px; }
.metadata-table td { padding: 6px 0; color: #374151; vertical-align: top; }
.metadata-table td:first-child { font-weight: bold; color: #6b7280; width: 40%; padding-right: 12px; }
.footer { background: #f9fafb; border-top: 1px solid #e5e7eb; padding: 20px 40px; text-align: center; color: #9ca3af; font-size: 12px; }
.footer a { color: #4f46e5; text-decoration: none; }
</style>
</head>
<body>
<div class="wrapper">
<div class="header">
{% if has_logo %}
<img src="cid:logo" alt="{{ app_name }} logo">
{% endif %}
<h1>Document Delivery</h1>
</div>
<div class="body">
<p>{{ message }}</p>
<div class="attachment-box">
<div class="label">Attached file</div>
<div class="filename">📎 {{ filename }}</div>
</div>
{% if has_metadata and metadata %}
<p style="font-weight:bold; color:#374151; margin-bottom:8px;">Document metadata</p>
<table class="metadata-table">
{% for key, value in metadata.items() %}
<tr>
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
<p style="margin-top:24px; color:#6b7280; font-size:13px;">
This document was sent automatically by {{ app_name }}.{% if app_url %} Visit <a href="{{ app_url }}" style="color:#4f46e5;">{{ app_url }}</a> to manage your documents.{% endif %}
</p>
</div>
<div class="footer">
&copy; {{ current_year }} {{ app_name }} &middot; Intelligent Document Processing
{% if app_url %}&middot; <a href="{{ app_url }}">{{ app_url }}</a>{% endif %}
</div>
</div>
</body>
</html>
+9 -9
View File
@@ -154,23 +154,23 @@ def get_provider_status() -> dict[str, dict[str, object]]:
},
}
# Add Email configuration
# Add Email destination configuration (dedicated settings for document delivery)
providers["Email"] = {
"name": "Email",
"icon": "fa-solid fa-envelope",
"configured": bool(
getattr(settings, "email_host", None) and getattr(settings, "email_default_recipient", None)
getattr(settings, "dest_email_host", None) and getattr(settings, "dest_email_default_recipient", None)
),
"enabled": True,
"description": "Send documents via email",
"details": {
"host": getattr(settings, "email_host", "Not set"),
"port": getattr(settings, "email_port", "Not set"),
"username": getattr(settings, "email_username", "Not set"),
"password": mask_sensitive_value(getattr(settings, "email_password", None)),
"use_tls": getattr(settings, "email_use_tls", "Not set"),
"sender": getattr(settings, "email_sender", "Not set"),
"default_recipient": getattr(settings, "email_default_recipient", "Not set"),
"host": getattr(settings, "dest_email_host", "Not set"),
"port": getattr(settings, "dest_email_port", "Not set"),
"username": getattr(settings, "dest_email_username", "Not set"),
"password": mask_sensitive_value(getattr(settings, "dest_email_password", None)),
"use_tls": getattr(settings, "dest_email_use_tls", "Not set"),
"sender": getattr(settings, "dest_email_sender", "Not set"),
"default_recipient": getattr(settings, "dest_email_default_recipient", "Not set"),
},
}
+5 -5
View File
@@ -107,12 +107,12 @@ def validate_storage_configs() -> dict[str, list[str]]:
issues["sftp"] = sftp_issues
# Validate Email sending
# Validate Email sending (destination-specific settings)
email_issues = []
if not getattr(settings, "email_host", None):
email_issues.append("EMAIL_HOST is not configured")
if not getattr(settings, "email_default_recipient", None):
email_issues.append("EMAIL_DEFAULT_RECIPIENT is not configured")
if not getattr(settings, "dest_email_host", None):
email_issues.append("DEST_EMAIL_HOST is not configured")
if not getattr(settings, "dest_email_default_recipient", None):
email_issues.append("DEST_EMAIL_DEFAULT_RECIPIENT is not configured")
issues["email"] = email_issues
# Validate S3
+59 -2
View File
@@ -917,7 +917,7 @@ SETTING_METADATA = {
# Email Settings
"email_host": {
"category": "Email",
"description": "SMTP server hostname",
"description": "SMTP server hostname (shared used for password reset and verification emails)",
"type": "string",
"sensitive": False,
"required": False,
@@ -965,7 +965,64 @@ SETTING_METADATA = {
},
"email_default_recipient": {
"category": "Email",
"description": "Default recipient email address",
"description": "Default recipient email address (shared used for system notifications)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
# Email Destination Settings (dedicated SMTP for document delivery)
"dest_email_host": {
"category": "Email Destination",
"description": "SMTP server hostname for document delivery (separate from shared email settings)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"dest_email_port": {
"category": "Email Destination",
"description": "SMTP port for document delivery (default: 587)",
"type": "integer",
"sensitive": False,
"required": False,
"restart_required": False,
},
"dest_email_username": {
"category": "Email Destination",
"description": "SMTP username for document delivery",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"dest_email_password": {
"category": "Email Destination",
"description": "SMTP password for document delivery",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": False,
},
"dest_email_use_tls": {
"category": "Email Destination",
"description": "Use TLS encryption for document delivery SMTP",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"dest_email_sender": {
"category": "Email Destination",
"description": "From address for document delivery emails (defaults to dest_email_username)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"dest_email_default_recipient": {
"category": "Email Destination",
"description": "Default recipient email for document delivery when none is specified",
"type": "string",
"sensitive": False,
"required": False,
+31 -4
View File
@@ -954,7 +954,11 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS
| `SFTP_PRIVATE_KEY` | Path to private key file for authentication (optional). |
| `SFTP_PRIVATE_KEY_PASSPHRASE`| Passphrase for private key if required (optional). |
### Email
### Email (shared SMTP password reset & verification)
> **Note:** These settings configure the shared SMTP connection used for system emails such as
> password resets and account verification. They do **not** enable the email delivery destination.
> To send processed documents via email, configure the dedicated `DEST_EMAIL_*` variables below.
| **Variable** | **Description** |
|----------------------------|----------------------------------------------------------|
@@ -964,7 +968,22 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS
| `EMAIL_PASSWORD` | SMTP authentication password. |
| `EMAIL_USE_TLS` | Whether to use TLS (default: `True`). |
| `EMAIL_SENDER` | From address (e.g., `"DocuElevate <docuelevate@example.com>"`). |
| `EMAIL_DEFAULT_RECIPIENT` | Default recipient email if none specified in the task. |
### Email Destination (document delivery)
> **Note:** These settings are intentionally separate from the shared `EMAIL_*` settings above.
> Configuring `EMAIL_HOST` for password resets does **not** automatically activate the email
> delivery destination. You must set `DEST_EMAIL_HOST` to enable it.
| **Variable** | **Description** |
|----------------------------------|---------------------------------------------------------------------|
| `DEST_EMAIL_HOST` | SMTP server hostname for document delivery. |
| `DEST_EMAIL_PORT` | SMTP port for document delivery (default: `587`). |
| `DEST_EMAIL_USERNAME` | SMTP authentication username for document delivery. |
| `DEST_EMAIL_PASSWORD` | SMTP authentication password for document delivery. |
| `DEST_EMAIL_USE_TLS` | Whether to use TLS for document delivery (default: `True`). |
| `DEST_EMAIL_SENDER` | From address for delivered documents (e.g., `"DocuElevate Delivery <docuelevate@example.com>"`). |
| `DEST_EMAIL_DEFAULT_RECIPIENT` | Fallback recipient email when none is specified for a delivery task. |
### OneDrive / Microsoft Graph
@@ -1362,14 +1381,22 @@ SFTP_FOLDER=/Documents/Uploads
# SFTP_PRIVATE_KEY=/path/to/key.pem
# SFTP_PRIVATE_KEY_PASSPHRASE=passphrase
# Email
# Email (shared SMTP password reset & verification)
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USERNAME=docuelevate@example.com
EMAIL_PASSWORD=password
EMAIL_USE_TLS=True
EMAIL_SENDER=DocuElevate System <docuelevate@example.com>
EMAIL_DEFAULT_RECIPIENT=recipient@example.com
# Email Destination (document delivery separate from shared email above)
DEST_EMAIL_HOST=smtp.example.com
DEST_EMAIL_PORT=587
DEST_EMAIL_USERNAME=docuelevate@example.com
DEST_EMAIL_PASSWORD=password
DEST_EMAIL_USE_TLS=True
DEST_EMAIL_SENDER=DocuElevate Delivery <docuelevate@example.com>
DEST_EMAIL_DEFAULT_RECIPIENT=recipient@example.com
# Notification Settings
# Configure notification services using Apprise URL format
+7 -7
View File
@@ -75,13 +75,13 @@ class TestValidateStorageConfigs:
assert "Neither SFTP_KEY_PATH nor SFTP_PASSWORD is configured" in result["sftp"]
def test_email_storage_missing_config(self):
"""Test validation when email storage config is missing."""
"""Test validation when email destination storage config is missing."""
with patch("app.utils.config_validator.validators.settings") as mock_settings:
mock_settings.email_host = None
mock_settings.email_default_recipient = None
mock_settings.dest_email_host = None
mock_settings.dest_email_default_recipient = None
result = validate_storage_configs()
assert "EMAIL_HOST is not configured" in result["email"]
assert "EMAIL_DEFAULT_RECIPIENT is not configured" in result["email"]
assert "DEST_EMAIL_HOST is not configured" in result["email"]
assert "DEST_EMAIL_DEFAULT_RECIPIENT is not configured" in result["email"]
@pytest.mark.unit
@@ -438,8 +438,8 @@ class TestValidateStorageConfigsEdgeCases:
# Configure all services
mock_settings.sftp_host = "sftp.example.com"
mock_settings.sftp_password = "pass"
mock_settings.email_host = "smtp.example.com"
mock_settings.email_default_recipient = "test@example.com"
mock_settings.dest_email_host = "smtp.example.com"
mock_settings.dest_email_default_recipient = "test@example.com"
mock_settings.s3_bucket_name = "my-bucket"
mock_settings.aws_access_key_id = "key"
mock_settings.aws_secret_access_key = "secret"
+7
View File
@@ -620,6 +620,13 @@ def _set_minimal_provider_settings(mock_settings):
"email_password": None,
"email_use_tls": True,
"email_sender": None,
"dest_email_host": None,
"dest_email_default_recipient": None,
"dest_email_port": 587,
"dest_email_username": None,
"dest_email_password": None,
"dest_email_use_tls": True,
"dest_email_sender": None,
"ftp_host": None,
"ftp_username": None,
"ftp_password": None,
+4 -4
View File
@@ -120,10 +120,10 @@ class TestShouldUploadFunctions:
@patch("app.tasks.send_to_all.settings")
def test_should_upload_to_email_configured(self, mock_settings):
"""Test email upload check."""
mock_settings.email_host = "smtp.example.com"
mock_settings.email_username = "user"
mock_settings.email_password = "pass"
mock_settings.email_default_recipient = "recipient@example.com"
mock_settings.dest_email_host = "smtp.example.com"
mock_settings.dest_email_username = "user"
mock_settings.dest_email_password = "pass"
mock_settings.dest_email_default_recipient = "recipient@example.com"
assert _should_upload_to_email() is True
+25 -25
View File
@@ -229,7 +229,7 @@ class TestPrepareRecipients:
@patch("app.tasks.upload_to_email.settings")
def test_uses_default_recipient_when_none_provided(self, mock_settings):
"""Test uses default recipient when none provided."""
mock_settings.email_default_recipient = "default@example.com"
mock_settings.dest_email_default_recipient = "default@example.com"
result, error = _prepare_recipients(None)
@@ -239,7 +239,7 @@ class TestPrepareRecipients:
@patch("app.tasks.upload_to_email.settings")
def test_returns_error_when_no_recipients_and_no_default(self, mock_settings):
"""Test returns error when no recipients and no default."""
mock_settings.email_default_recipient = None
mock_settings.dest_email_default_recipient = None
result, error = _prepare_recipients(None)
@@ -256,11 +256,11 @@ class TestSendEmailWithSMTP:
@patch("app.tasks.upload_to_email.settings")
def test_sends_email_successfully(self, mock_settings, mock_gethostbyname, mock_smtp):
"""Test sends email successfully."""
mock_settings.email_host = "smtp.example.com"
mock_settings.email_port = 587
mock_settings.email_use_tls = True
mock_settings.email_username = "user@example.com"
mock_settings.email_password = "password"
mock_settings.dest_email_host = "smtp.example.com"
mock_settings.dest_email_port = 587
mock_settings.dest_email_use_tls = True
mock_settings.dest_email_username = "user@example.com"
mock_settings.dest_email_password = "password"
mock_server = MagicMock()
mock_smtp.return_value.__enter__.return_value = mock_server
@@ -292,8 +292,8 @@ class TestSendEmailWithSMTP:
@patch("app.tasks.upload_to_email.settings")
def test_handles_connection_refused_error(self, mock_settings, mock_gethostbyname, mock_smtp):
"""Test handles connection refused error."""
mock_settings.email_host = "smtp.example.com"
mock_settings.email_port = 587
mock_settings.dest_email_host = "smtp.example.com"
mock_settings.dest_email_port = 587
mock_smtp.return_value.__enter__.side_effect = ConnectionRefusedError("Connection refused")
@@ -309,11 +309,11 @@ class TestSendEmailWithSMTP:
@patch("app.tasks.upload_to_email.settings")
def test_sends_email_without_tls(self, mock_settings, mock_gethostbyname, mock_smtp):
"""Test sends email without TLS."""
mock_settings.email_host = "smtp.example.com"
mock_settings.email_port = 25
mock_settings.email_use_tls = False
mock_settings.email_username = "user@example.com"
mock_settings.email_password = "password"
mock_settings.dest_email_host = "smtp.example.com"
mock_settings.dest_email_port = 25
mock_settings.dest_email_use_tls = False
mock_settings.dest_email_username = "user@example.com"
mock_settings.dest_email_password = "password"
mock_server = MagicMock()
mock_smtp.return_value.__enter__.return_value = mock_server
@@ -333,11 +333,11 @@ class TestSendEmailWithSMTP:
@patch("app.tasks.upload_to_email.settings")
def test_sends_email_without_authentication(self, mock_settings, mock_gethostbyname, mock_smtp):
"""Test sends email without authentication credentials."""
mock_settings.email_host = "smtp.example.com"
mock_settings.email_port = 25
mock_settings.email_use_tls = False
mock_settings.email_username = None
mock_settings.email_password = None
mock_settings.dest_email_host = "smtp.example.com"
mock_settings.dest_email_port = 25
mock_settings.dest_email_use_tls = False
mock_settings.dest_email_username = None
mock_settings.dest_email_password = None
mock_server = MagicMock()
mock_smtp.return_value.__enter__.return_value = mock_server
@@ -356,8 +356,8 @@ class TestSendEmailWithSMTP:
@patch("app.tasks.upload_to_email.settings")
def test_handles_timeout_error(self, mock_settings, mock_gethostbyname, mock_smtp):
"""Test handles timeout error."""
mock_settings.email_host = "smtp.example.com"
mock_settings.email_port = 587
mock_settings.dest_email_host = "smtp.example.com"
mock_settings.dest_email_port = 587
mock_smtp.return_value.__enter__.side_effect = TimeoutError("Connection timeout")
@@ -392,10 +392,10 @@ class TestUploadToEmailTask:
@patch("app.tasks.upload_to_email.os.path.exists")
@patch("app.tasks.upload_to_email.settings")
def test_skips_when_email_host_not_configured(self, mock_settings, mock_exists, mock_log, mock_basename):
"""Test skips when email host not configured."""
"""Test skips when email destination host not configured."""
mock_exists.return_value = True
mock_basename.return_value = "test.pdf"
mock_settings.email_host = None
mock_settings.dest_email_host = None
mock_self = Mock()
mock_self.request.id = "test-task-id"
@@ -403,7 +403,7 @@ class TestUploadToEmailTask:
result = upload_to_email(mock_self, "/tmp/test.pdf")
assert result["status"] == "Skipped"
assert "Email host is not configured" in result["reason"]
assert "DEST_EMAIL_HOST" in result["reason"]
@patch("app.tasks.upload_to_email.os.path.basename")
@patch("app.tasks.upload_to_email._prepare_recipients")
@@ -414,7 +414,7 @@ class TestUploadToEmailTask:
"""Test skips when no valid recipients."""
mock_exists.return_value = True
mock_basename.return_value = "test.pdf"
mock_settings.email_host = "smtp.example.com"
mock_settings.dest_email_host = "smtp.example.com"
mock_prepare.return_value = (None, "No recipients specified")
mock_self = Mock()
+7 -7
View File
@@ -364,12 +364,12 @@ def test_upload_to_email_accepts_file_id(sample_text_file):
patch("app.tasks.upload_to_email.attach_logo") as mock_logo,
):
# Setup settings
mock_settings.email_host = "smtp.example.com"
mock_settings.email_port = 587
mock_settings.email_username = "test@example.com"
mock_settings.email_password = _TEST_CREDENTIAL
mock_settings.email_use_tls = True
mock_settings.email_sender = "sender@example.com"
mock_settings.dest_email_host = "smtp.example.com"
mock_settings.dest_email_port = 587
mock_settings.dest_email_username = "test@example.com"
mock_settings.dest_email_password = _TEST_CREDENTIAL
mock_settings.dest_email_use_tls = True
mock_settings.dest_email_sender = "sender@example.com"
mock_settings.external_hostname = "docuelevate.example.com"
# Setup mocks
@@ -503,7 +503,7 @@ def test_send_to_all_calls_upload_tasks_with_keyword_argument():
mock_settings.webdav_url = None
mock_settings.ftp_host = None
mock_settings.sftp_host = None
mock_settings.email_host = None
mock_settings.dest_email_host = None
mock_settings.onedrive_client_id = None
mock_settings.workdir = "/tmp"