"""Tests for app/tasks/upload_to_sftp.py module.""" from unittest.mock import MagicMock, patch import paramiko import pytest from app.tasks.upload_to_sftp import upload_to_sftp @pytest.mark.unit class TestUploadToSFTP: """Tests for SFTP upload functionality.""" @patch("app.tasks.upload_to_sftp.paramiko.SSHClient") @patch("app.tasks.upload_to_sftp.settings") def test_upload_with_key_authentication(self, mock_settings, mock_ssh_class, tmp_path): """Test SFTP upload using SSH key authentication.""" # Setup test_file = tmp_path / "test.pdf" test_file.write_text("test content") mock_settings.sftp_host = "sftp.example.com" mock_settings.sftp_port = 22 mock_settings.sftp_username = "testuser" mock_settings.sftp_password = None mock_settings.sftp_private_key = str(tmp_path / "key.pem") mock_settings.sftp_private_key_passphrase = "passphrase" mock_settings.sftp_folder = "/uploads" mock_settings.workdir = str(tmp_path) mock_settings.sftp_disable_host_key_verification = False # Create mock key file key_file = tmp_path / "key.pem" key_file.write_text("fake key") # Mock SSH and SFTP mock_ssh = MagicMock() mock_sftp = MagicMock() mock_ssh.open_sftp.return_value = mock_sftp mock_sftp.stat.side_effect = FileNotFoundError mock_ssh_class.return_value = mock_ssh # Execute task = upload_to_sftp.apply(args=[str(test_file)]) # Verify assert task.result["status"] == "Completed" mock_ssh.connect.assert_called_once() connect_kwargs = mock_ssh.connect.call_args[1] assert connect_kwargs["key_filename"] == str(tmp_path / "key.pem") assert connect_kwargs["passphrase"] == "passphrase" mock_sftp.put.assert_called_once() @patch("app.tasks.upload_to_sftp.paramiko.SSHClient") @patch("app.tasks.upload_to_sftp.settings") def test_upload_with_password_authentication(self, mock_settings, mock_ssh_class, tmp_path): """Test SFTP upload using password authentication.""" # Setup test_file = tmp_path / "test.pdf" test_file.write_text("test content") mock_settings.sftp_host = "sftp.example.com" mock_settings.sftp_port = 22 mock_settings.sftp_username = "testuser" mock_settings.sftp_password = "testpass" mock_settings.sftp_private_key = None mock_settings.sftp_folder = "/uploads" mock_settings.workdir = str(tmp_path) mock_settings.sftp_disable_host_key_verification = False # Mock SSH and SFTP mock_ssh = MagicMock() mock_sftp = MagicMock() mock_ssh.open_sftp.return_value = mock_sftp mock_sftp.stat.side_effect = FileNotFoundError mock_ssh_class.return_value = mock_ssh # Execute task = upload_to_sftp.apply(args=[str(test_file)]) # Verify assert task.result["status"] == "Completed" connect_kwargs = mock_ssh.connect.call_args[1] assert connect_kwargs["password"] == "testpass" assert "key_filename" not in connect_kwargs @patch("app.tasks.upload_to_sftp.paramiko.SSHClient") @patch("app.tasks.upload_to_sftp.settings") def test_upload_with_disabled_host_key_verification(self, mock_settings, mock_ssh_class, tmp_path): """Test SFTP upload with host key verification disabled.""" test_file = tmp_path / "test.pdf" test_file.write_text("test content") mock_settings.sftp_host = "sftp.example.com" mock_settings.sftp_port = 22 mock_settings.sftp_username = "testuser" mock_settings.sftp_password = "testpass" mock_settings.sftp_folder = "" mock_settings.workdir = str(tmp_path) mock_settings.sftp_disable_host_key_verification = True mock_ssh = MagicMock() mock_sftp = MagicMock() mock_ssh.open_sftp.return_value = mock_sftp mock_sftp.stat.side_effect = FileNotFoundError mock_ssh_class.return_value = mock_ssh # Execute task = upload_to_sftp.apply(args=[str(test_file)]) # Verify AutoAddPolicy was set mock_ssh.set_missing_host_key_policy.assert_called() # Check that it was called with AutoAddPolicy (not RejectPolicy) call_arg = mock_ssh.set_missing_host_key_policy.call_args[0][0] assert isinstance(call_arg, paramiko.AutoAddPolicy) @patch("app.tasks.upload_to_sftp.settings") def test_upload_file_not_found(self, mock_settings): """Test SFTP upload with non-existent file.""" mock_settings.sftp_host = "sftp.example.com" mock_settings.sftp_port = 22 mock_settings.sftp_username = "testuser" result = upload_to_sftp.apply(args=["/nonexistent/file.pdf"]) assert isinstance(result.result, Exception) assert "File not found" in str(result.result) @patch("app.tasks.upload_to_sftp.settings") def test_upload_missing_configuration(self, mock_settings, tmp_path): """Test SFTP upload with missing configuration.""" test_file = tmp_path / "test.pdf" test_file.write_text("test content") mock_settings.sftp_host = None mock_settings.sftp_port = None mock_settings.sftp_username = None result = upload_to_sftp.apply(args=[str(test_file)]) assert result.result["status"] == "Skipped" assert "not configured" in result.result["reason"] @patch("app.tasks.upload_to_sftp.paramiko.SSHClient") @patch("app.tasks.upload_to_sftp.settings") def test_upload_no_authentication_method(self, mock_settings, mock_ssh_class, tmp_path): """Test SFTP upload with no authentication method available.""" test_file = tmp_path / "test.pdf" test_file.write_text("test content") mock_settings.sftp_host = "sftp.example.com" mock_settings.sftp_port = 22 mock_settings.sftp_username = "testuser" mock_settings.sftp_password = None mock_settings.sftp_private_key = None mock_settings.sftp_folder = "" mock_settings.workdir = str(tmp_path) mock_settings.sftp_disable_host_key_verification = False mock_ssh = MagicMock() mock_ssh_class.return_value = mock_ssh result = upload_to_sftp.apply(args=[str(test_file)]) assert isinstance(result.result, Exception) assert "No authentication method" in str(result.result) @patch("app.tasks.upload_to_sftp.paramiko.SSHClient") @patch("app.tasks.upload_to_sftp.settings") def test_upload_creates_remote_directories(self, mock_settings, mock_ssh_class, tmp_path): """Test that remote directories are created as needed.""" test_file = tmp_path / "test.pdf" test_file.write_text("test content") mock_settings.sftp_host = "sftp.example.com" mock_settings.sftp_port = 22 mock_settings.sftp_username = "testuser" mock_settings.sftp_password = "testpass" mock_settings.sftp_folder = "/remote/nested/path" mock_settings.workdir = str(tmp_path) mock_settings.sftp_disable_host_key_verification = False mock_ssh = MagicMock() mock_sftp = MagicMock() mock_ssh.open_sftp.return_value = mock_sftp # Simulate directories not existing mock_sftp.stat.side_effect = FileNotFoundError mock_ssh_class.return_value = mock_ssh # Execute task = upload_to_sftp.apply(args=[str(test_file)]) # Verify mkdir was called for each directory level mkdir_calls = [call[0][0] for call in mock_sftp.mkdir.call_args_list] assert any("/remote" in call for call in mkdir_calls) assert any("/remote/nested" in call for call in mkdir_calls) assert any("/remote/nested/path" in call for call in mkdir_calls) @patch("app.tasks.upload_to_sftp.paramiko.SSHClient") @patch("app.tasks.upload_to_sftp.settings") def test_upload_connection_error_cleanup(self, mock_settings, mock_ssh_class, tmp_path): """Test that connections are cleaned up on error.""" test_file = tmp_path / "test.pdf" test_file.write_text("test content") mock_settings.sftp_host = "sftp.example.com" mock_settings.sftp_port = 22 mock_settings.sftp_username = "testuser" mock_settings.sftp_password = "testpass" mock_settings.sftp_private_key = None mock_settings.sftp_folder = "" mock_settings.workdir = str(tmp_path) mock_settings.sftp_disable_host_key_verification = False mock_ssh = MagicMock() mock_sftp = MagicMock() mock_ssh.open_sftp.return_value = mock_sftp mock_sftp.put.side_effect = Exception("Upload failed") mock_ssh_class.return_value = mock_ssh result = upload_to_sftp.apply(args=[str(test_file)]) assert isinstance(result.result, Exception) assert "Upload failed" in str(result.result) # Verify cleanup was attempted mock_sftp.close.assert_called() mock_ssh.close.assert_called()