Files
gh-christianlouis-docuelevate/tests/test_upload_ftp_additional.py
T
copilot-swe-agent[bot] bc435ed8cb feat(tests): add comprehensive test coverage for 6 modules (part 1)
- Enhanced test_upload_email.py: Added 20+ tests for email upload task (get_email_template, extract_metadata, attach_logo, prepare_recipients, send_email, upload_to_email)
- Enhanced test_api_settings.py: Added 10+ tests for settings API endpoints and models
- Created test_upload_google_drive.py: Added 25+ tests for Google Drive upload (OAuth, service account, metadata, truncation)
- Enhanced test_views_settings.py: Added 15+ tests for settings view and admin access
- Enhanced test_upload_ftp_additional.py: Added 18+ tests for FTP upload (FTPS, plaintext, directory creation, error handling)
- Enhanced test_security_headers.py: Added 12+ tests for security headers middleware
- Enhanced test_check_credentials.py: Added 15+ tests for credential checking task
- Enhanced test_views_status.py: Added 15+ tests for status dashboard and env debug views

Target: Reach ≥80% coverage for 9 modules

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
2026-02-13 11:34:02 +00:00

297 lines
11 KiB
Python

"""Additional tests for upload_to_ftp task."""
import ftplib
from unittest.mock import MagicMock, Mock, patch
import pytest
from app.tasks.upload_to_ftp import upload_to_ftp
@pytest.mark.unit
class TestUploadToFtp:
"""Tests for upload_to_ftp task."""
def test_module_imports(self):
"""Test that the module can be imported."""
from app.tasks.upload_to_ftp import upload_to_ftp
assert callable(upload_to_ftp)
@patch("app.tasks.upload_to_ftp.ftplib.FTP_TLS")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
@patch("builtins.open", create=True)
def test_uploads_file_with_ftps(self, mock_open, mock_settings, mock_exists, mock_log, mock_ftp_tls):
"""Test uploads file using FTPS (FTP with TLS)."""
mock_exists.return_value = True
mock_settings.ftp_host = "ftp.example.com"
mock_settings.ftp_port = 21
mock_settings.ftp_username = "user"
mock_settings.ftp_password = "password"
mock_settings.ftp_folder = "/uploads"
mock_settings.ftp_use_tls = True
mock_settings.ftp_allow_plaintext = True
mock_ftp = Mock()
mock_ftp_tls.return_value = mock_ftp
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
result = task("/tmp/test.pdf")
assert result["status"] == "Completed"
assert result["used_tls"] is True
mock_ftp.login.assert_called_once()
mock_ftp.prot_p.assert_called_once()
@patch("app.tasks.upload_to_ftp.ftplib.FTP")
@patch("app.tasks.upload_to_ftp.ftplib.FTP_TLS")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
@patch("builtins.open", create=True)
def test_falls_back_to_plaintext_ftp(
self, mock_open, mock_settings, mock_exists, mock_log, mock_ftp_tls, mock_ftp
):
"""Test falls back to plaintext FTP when FTPS fails."""
mock_exists.return_value = True
mock_settings.ftp_host = "ftp.example.com"
mock_settings.ftp_port = 21
mock_settings.ftp_username = "user"
mock_settings.ftp_password = "password"
mock_settings.ftp_folder = None
mock_settings.ftp_use_tls = True
mock_settings.ftp_allow_plaintext = True
# FTPS fails
mock_ftp_tls_instance = Mock()
mock_ftp_tls_instance.connect.side_effect = Exception("TLS not supported")
mock_ftp_tls.return_value = mock_ftp_tls_instance
# Plaintext FTP succeeds
mock_ftp_instance = Mock()
mock_ftp.return_value = mock_ftp_instance
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
result = task("/tmp/test.pdf")
assert result["status"] == "Completed"
assert result["used_tls"] is False
@patch("app.tasks.upload_to_ftp.ftplib.FTP_TLS")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
def test_raises_error_when_ftps_fails_and_plaintext_forbidden(
self, mock_settings, mock_exists, mock_log, mock_ftp_tls
):
"""Test raises error when FTPS fails and plaintext is forbidden."""
mock_exists.return_value = True
mock_settings.ftp_host = "ftp.example.com"
mock_settings.ftp_port = 21
mock_settings.ftp_username = "user"
mock_settings.ftp_password = "password"
mock_settings.ftp_folder = None
mock_settings.ftp_use_tls = True
mock_settings.ftp_allow_plaintext = False
mock_ftp_tls_instance = Mock()
mock_ftp_tls_instance.connect.side_effect = Exception("TLS not supported")
mock_ftp_tls.return_value = mock_ftp_tls_instance
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
with pytest.raises(Exception, match="FTPS connection failed and plaintext FTP is forbidden"):
task("/tmp/test.pdf")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
def test_raises_error_when_file_not_found(self, mock_exists, mock_log):
"""Test raises error when file not found."""
mock_exists.return_value = False
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
with pytest.raises(FileNotFoundError):
task("/nonexistent/file.pdf")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
def test_raises_error_when_ftp_host_not_configured(self, mock_settings, mock_exists, mock_log):
"""Test raises error when FTP host not configured."""
mock_exists.return_value = True
mock_settings.ftp_host = None
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
with pytest.raises(ValueError, match="FTP host is not configured"):
task("/tmp/test.pdf")
@patch("app.tasks.upload_to_ftp.ftplib.FTP_TLS")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
@patch("builtins.open", create=True)
def test_creates_directory_structure(self, mock_open, mock_settings, mock_exists, mock_log, mock_ftp_tls):
"""Test creates directory structure if it doesn't exist."""
mock_exists.return_value = True
mock_settings.ftp_host = "ftp.example.com"
mock_settings.ftp_port = 21
mock_settings.ftp_username = "user"
mock_settings.ftp_password = "password"
mock_settings.ftp_folder = "/uploads/documents"
mock_settings.ftp_use_tls = True
mock_ftp = Mock()
mock_ftp.cwd.side_effect = [ftplib.error_perm("No such directory"), None]
mock_ftp_tls.return_value = mock_ftp
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
result = task("/tmp/test.pdf")
assert result["status"] == "Completed"
mock_ftp.mkd.assert_called()
@patch("app.tasks.upload_to_ftp.ftplib.FTP")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
@patch("builtins.open", create=True)
def test_uses_plaintext_ftp_when_tls_disabled(self, mock_open, mock_settings, mock_exists, mock_log, mock_ftp):
"""Test uses plaintext FTP when TLS is explicitly disabled."""
mock_exists.return_value = True
mock_settings.ftp_host = "ftp.example.com"
mock_settings.ftp_port = 21
mock_settings.ftp_username = "user"
mock_settings.ftp_password = "password"
mock_settings.ftp_folder = None
mock_settings.ftp_use_tls = False
mock_settings.ftp_allow_plaintext = True
mock_ftp_instance = Mock()
mock_ftp.return_value = mock_ftp_instance
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
result = task("/tmp/test.pdf")
assert result["status"] == "Completed"
assert result["used_tls"] is False
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
def test_raises_error_when_plaintext_forbidden_and_tls_disabled(self, mock_settings, mock_exists, mock_log):
"""Test raises error when plaintext is forbidden and TLS is disabled."""
mock_exists.return_value = True
mock_settings.ftp_host = "ftp.example.com"
mock_settings.ftp_port = 21
mock_settings.ftp_use_tls = False
mock_settings.ftp_allow_plaintext = False
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
with pytest.raises(Exception, match="Plaintext FTP is forbidden"):
task("/tmp/test.pdf")
@patch("app.tasks.upload_to_ftp.ftplib.FTP_TLS")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
@patch("builtins.open", create=True)
def test_removes_leading_slash_from_folder(self, mock_open, mock_settings, mock_exists, mock_log, mock_ftp_tls):
"""Test removes leading slash from folder path."""
mock_exists.return_value = True
mock_settings.ftp_host = "ftp.example.com"
mock_settings.ftp_port = 21
mock_settings.ftp_username = "user"
mock_settings.ftp_password = "password"
mock_settings.ftp_folder = "/uploads"
mock_settings.ftp_use_tls = True
mock_ftp = Mock()
mock_ftp_tls.return_value = mock_ftp
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
task("/tmp/test.pdf")
# Verify cwd was called with folder without leading slash
mock_ftp.cwd.assert_called_with("uploads")
@patch("app.tasks.upload_to_ftp.ftplib.FTP_TLS")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
def test_handles_directory_creation_error(self, mock_settings, mock_exists, mock_log, mock_ftp_tls):
"""Test handles directory creation error."""
mock_exists.return_value = True
mock_settings.ftp_host = "ftp.example.com"
mock_settings.ftp_port = 21
mock_settings.ftp_username = "user"
mock_settings.ftp_password = "password"
mock_settings.ftp_folder = "/uploads"
mock_settings.ftp_use_tls = True
mock_ftp = Mock()
mock_ftp.cwd.side_effect = ftplib.error_perm("Permission denied")
mock_ftp.mkd.side_effect = ftplib.error_perm("Cannot create directory")
mock_ftp_tls.return_value = mock_ftp
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
with pytest.raises(Exception, match="Failed to change/create directory"):
task("/tmp/test.pdf")
@patch("app.tasks.upload_to_ftp.ftplib.FTP_TLS")
@patch("app.tasks.upload_to_ftp.log_task_progress")
@patch("app.tasks.upload_to_ftp.os.path.exists")
@patch("app.tasks.upload_to_ftp.settings")
@patch("builtins.open", create=True)
def test_returns_ftp_path_in_result(self, mock_open, mock_settings, mock_exists, mock_log, mock_ftp_tls):
"""Test returns FTP path in result."""
mock_exists.return_value = True
mock_settings.ftp_host = "ftp.example.com"
mock_settings.ftp_port = 21
mock_settings.ftp_username = "user"
mock_settings.ftp_password = "password"
mock_settings.ftp_folder = "/uploads"
mock_settings.ftp_use_tls = True
mock_ftp = Mock()
mock_ftp_tls.return_value = mock_ftp
task = upload_to_ftp
task.request = Mock()
task.request.id = "test-task-id"
result = task("/tmp/test.pdf")
assert "ftp_path" in result
assert result["ftp_path"] == "/uploads/test.pdf"