dff32e08e1
- Add tests for migrate_logs_to_steps.py, upload_to_paperless.py, dropbox API, upload_to_dropbox.py, upload_to_nextcloud.py, upload_to_onedrive.py, upload_with_rclone.py, and config_validator.py - Fix PydanticDeprecatedSince20: @validator → @field_validator in config.py, url_upload.py - Fix PydanticDeprecatedSince20: class Config → model_config = SettingsConfigDict - Fix PydanticDeprecatedSince211: filter Pydantic internals in settings_display.py - Fix MovedIn20Warning: use sqlalchemy.orm.declarative_base instead of ext.declarative Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
334 lines
13 KiB
Python
334 lines
13 KiB
Python
"""
|
|
Tests for app/tasks/upload_to_dropbox.py module.
|
|
|
|
Covers _validate_dropbox_settings, get_dropbox_access_token, get_dropbox_client,
|
|
and upload_to_dropbox Celery task.
|
|
"""
|
|
|
|
import os
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
from dropbox.exceptions import ApiError, AuthError
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestValidateDropboxSettings:
|
|
"""Tests for _validate_dropbox_settings function."""
|
|
|
|
def test_all_settings_present(self):
|
|
"""Test validation passes when all settings are present."""
|
|
from app.tasks.upload_to_dropbox import _validate_dropbox_settings
|
|
|
|
with patch("app.tasks.upload_to_dropbox.settings") as mock_settings:
|
|
mock_settings.dropbox_refresh_token = "token"
|
|
mock_settings.dropbox_app_key = "key"
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
|
|
assert _validate_dropbox_settings() is True
|
|
|
|
def test_missing_refresh_token(self):
|
|
"""Test validation fails when refresh token is missing."""
|
|
from app.tasks.upload_to_dropbox import _validate_dropbox_settings
|
|
|
|
with patch("app.tasks.upload_to_dropbox.settings") as mock_settings:
|
|
mock_settings.dropbox_refresh_token = ""
|
|
mock_settings.dropbox_app_key = "key"
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
|
|
assert _validate_dropbox_settings() is False
|
|
|
|
def test_missing_app_key(self):
|
|
"""Test validation fails when app key is missing."""
|
|
from app.tasks.upload_to_dropbox import _validate_dropbox_settings
|
|
|
|
with patch("app.tasks.upload_to_dropbox.settings") as mock_settings:
|
|
mock_settings.dropbox_refresh_token = "token"
|
|
mock_settings.dropbox_app_key = ""
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
|
|
assert _validate_dropbox_settings() is False
|
|
|
|
def test_missing_app_secret(self):
|
|
"""Test validation fails when app secret is missing."""
|
|
from app.tasks.upload_to_dropbox import _validate_dropbox_settings
|
|
|
|
with patch("app.tasks.upload_to_dropbox.settings") as mock_settings:
|
|
mock_settings.dropbox_refresh_token = "token"
|
|
mock_settings.dropbox_app_key = "key"
|
|
mock_settings.dropbox_app_secret = ""
|
|
|
|
assert _validate_dropbox_settings() is False
|
|
|
|
def test_missing_all_settings(self):
|
|
"""Test validation fails when all settings are missing."""
|
|
from app.tasks.upload_to_dropbox import _validate_dropbox_settings
|
|
|
|
with patch("app.tasks.upload_to_dropbox.settings") as mock_settings:
|
|
mock_settings.dropbox_refresh_token = None
|
|
mock_settings.dropbox_app_key = None
|
|
mock_settings.dropbox_app_secret = None
|
|
|
|
assert _validate_dropbox_settings() is False
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestGetDropboxAccessToken:
|
|
"""Tests for get_dropbox_access_token function."""
|
|
|
|
@patch("app.tasks.upload_to_dropbox.requests.post")
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_successful_refresh(self, mock_settings, mock_post):
|
|
"""Test successful token refresh."""
|
|
from app.tasks.upload_to_dropbox import get_dropbox_access_token
|
|
|
|
mock_settings.dropbox_refresh_token = "refresh-token"
|
|
mock_settings.dropbox_app_key = "app-key"
|
|
mock_settings.dropbox_app_secret = "app-secret"
|
|
mock_settings.http_request_timeout = 30
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {"access_token": "new-access-token"}
|
|
mock_post.return_value = mock_response
|
|
|
|
token = get_dropbox_access_token()
|
|
assert token == "new-access-token"
|
|
|
|
@patch("app.tasks.upload_to_dropbox.requests.post")
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_refresh_failure_raises(self, mock_settings, mock_post):
|
|
"""Test that failed token refresh raises exception."""
|
|
from app.tasks.upload_to_dropbox import get_dropbox_access_token
|
|
|
|
mock_settings.dropbox_refresh_token = "refresh-token"
|
|
mock_settings.dropbox_app_key = "app-key"
|
|
mock_settings.dropbox_app_secret = "app-secret"
|
|
mock_settings.http_request_timeout = 30
|
|
|
|
mock_response = Mock()
|
|
mock_response.status_code = 400
|
|
mock_response.text = "invalid_grant"
|
|
mock_post.return_value = mock_response
|
|
|
|
with pytest.raises(Exception, match="Failed to refresh"):
|
|
get_dropbox_access_token()
|
|
|
|
def test_returns_none_when_settings_missing(self):
|
|
"""Test returns None when settings are not configured."""
|
|
from app.tasks.upload_to_dropbox import get_dropbox_access_token
|
|
|
|
with patch("app.tasks.upload_to_dropbox.settings") as mock_settings:
|
|
mock_settings.dropbox_refresh_token = None
|
|
mock_settings.dropbox_app_key = None
|
|
mock_settings.dropbox_app_secret = None
|
|
|
|
result = get_dropbox_access_token()
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestGetDropboxClient:
|
|
"""Tests for get_dropbox_client function."""
|
|
|
|
@patch("app.tasks.upload_to_dropbox.dropbox.Dropbox")
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_successful_client_creation(self, mock_settings, mock_dropbox):
|
|
"""Test successful Dropbox client creation."""
|
|
from app.tasks.upload_to_dropbox import get_dropbox_client
|
|
|
|
mock_settings.dropbox_app_key = "app-key"
|
|
mock_settings.dropbox_app_secret = "app-secret"
|
|
mock_settings.dropbox_refresh_token = "refresh-token"
|
|
|
|
mock_instance = Mock()
|
|
mock_dropbox.return_value = mock_instance
|
|
|
|
client = get_dropbox_client()
|
|
assert client == mock_instance
|
|
mock_instance.users_get_current_account.assert_called_once()
|
|
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_missing_app_key_raises(self, mock_settings):
|
|
"""Test that missing app key raises ValueError."""
|
|
from app.tasks.upload_to_dropbox import get_dropbox_client
|
|
|
|
mock_settings.dropbox_app_key = ""
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
mock_settings.dropbox_refresh_token = "token"
|
|
|
|
with pytest.raises(ValueError, match="app key or app secret"):
|
|
get_dropbox_client()
|
|
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_missing_refresh_token_raises(self, mock_settings):
|
|
"""Test that missing refresh token raises ValueError."""
|
|
from app.tasks.upload_to_dropbox import get_dropbox_client
|
|
|
|
mock_settings.dropbox_app_key = "key"
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
mock_settings.dropbox_refresh_token = ""
|
|
|
|
with pytest.raises(ValueError, match="refresh token"):
|
|
get_dropbox_client()
|
|
|
|
@patch("app.tasks.upload_to_dropbox.dropbox.Dropbox")
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_auth_error_propagated(self, mock_settings, mock_dropbox):
|
|
"""Test that AuthError is propagated."""
|
|
from app.tasks.upload_to_dropbox import get_dropbox_client
|
|
|
|
mock_settings.dropbox_app_key = "key"
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
mock_settings.dropbox_refresh_token = "token"
|
|
|
|
mock_instance = Mock()
|
|
mock_instance.users_get_current_account.side_effect = AuthError("req-id", "Invalid token")
|
|
mock_dropbox.return_value = mock_instance
|
|
|
|
with pytest.raises(AuthError):
|
|
get_dropbox_client()
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestUploadToDropbox:
|
|
"""Tests for upload_to_dropbox Celery task."""
|
|
|
|
@patch("app.tasks.upload_to_dropbox.log_task_progress")
|
|
def test_file_not_found(self, mock_log):
|
|
"""Test that missing file raises FileNotFoundError."""
|
|
from app.tasks.upload_to_dropbox import upload_to_dropbox
|
|
|
|
mock_self = MagicMock()
|
|
mock_self.request.id = "test-task"
|
|
|
|
with pytest.raises(FileNotFoundError):
|
|
upload_to_dropbox.__wrapped__(mock_self, "/nonexistent/file.pdf", file_id=1)
|
|
|
|
@patch("app.tasks.upload_to_dropbox.log_task_progress")
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_skipped_when_not_configured(self, mock_settings, mock_log, tmp_path):
|
|
"""Test upload skipped when Dropbox not configured."""
|
|
from app.tasks.upload_to_dropbox import upload_to_dropbox
|
|
|
|
mock_settings.dropbox_app_key = ""
|
|
mock_settings.dropbox_app_secret = ""
|
|
mock_settings.dropbox_refresh_token = ""
|
|
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_bytes(b"test content")
|
|
|
|
result = upload_to_dropbox.apply(args=[str(test_file)], kwargs={"file_id": 1}).get()
|
|
|
|
assert result["status"] == "Skipped"
|
|
|
|
@patch("app.tasks.upload_to_dropbox.get_unique_filename")
|
|
@patch("app.tasks.upload_to_dropbox.extract_remote_path")
|
|
@patch("app.tasks.upload_to_dropbox.get_dropbox_client")
|
|
@patch("app.tasks.upload_to_dropbox.log_task_progress")
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_successful_small_file_upload(
|
|
self, mock_settings, mock_log, mock_client, mock_extract, mock_unique, tmp_path
|
|
):
|
|
"""Test successful upload of a small file."""
|
|
from app.tasks.upload_to_dropbox import upload_to_dropbox
|
|
|
|
mock_settings.dropbox_app_key = "key"
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
mock_settings.dropbox_refresh_token = "token"
|
|
mock_settings.dropbox_folder = "/uploads"
|
|
mock_settings.workdir = str(tmp_path)
|
|
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_bytes(b"small file content")
|
|
|
|
mock_dbx = Mock()
|
|
mock_client.return_value = mock_dbx
|
|
mock_extract.return_value = "uploads/test.pdf"
|
|
mock_unique.return_value = "/uploads/test.pdf"
|
|
|
|
result = upload_to_dropbox.apply(args=[str(test_file)], kwargs={"file_id": 1}).get()
|
|
|
|
assert result["status"] == "Completed"
|
|
assert result["file_path"] == str(test_file)
|
|
mock_dbx.files_upload.assert_called_once()
|
|
|
|
@patch("app.tasks.upload_to_dropbox.get_unique_filename")
|
|
@patch("app.tasks.upload_to_dropbox.extract_remote_path")
|
|
@patch("app.tasks.upload_to_dropbox.get_dropbox_client")
|
|
@patch("app.tasks.upload_to_dropbox.log_task_progress")
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_large_file_chunked_upload(
|
|
self, mock_settings, mock_log, mock_client, mock_extract, mock_unique, tmp_path
|
|
):
|
|
"""Test chunked upload for large files (>10MB)."""
|
|
from app.tasks.upload_to_dropbox import upload_to_dropbox
|
|
|
|
mock_settings.dropbox_app_key = "key"
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
mock_settings.dropbox_refresh_token = "token"
|
|
mock_settings.dropbox_folder = "/uploads"
|
|
mock_settings.workdir = str(tmp_path)
|
|
|
|
# Create a file larger than 10MB threshold
|
|
test_file = tmp_path / "large.pdf"
|
|
test_file.write_bytes(b"x" * (11 * 1024 * 1024))
|
|
|
|
mock_dbx = Mock()
|
|
mock_client.return_value = mock_dbx
|
|
mock_extract.return_value = "uploads/large.pdf"
|
|
mock_unique.return_value = "/uploads/large.pdf"
|
|
|
|
mock_session = Mock()
|
|
mock_session.session_id = "session-123"
|
|
mock_dbx.files_upload_session_start.return_value = mock_session
|
|
|
|
result = upload_to_dropbox.apply(args=[str(test_file)], kwargs={"file_id": 1}).get()
|
|
|
|
assert result["status"] == "Completed"
|
|
mock_dbx.files_upload_session_start.assert_called_once()
|
|
|
|
@patch("app.tasks.upload_to_dropbox.get_dropbox_client")
|
|
@patch("app.tasks.upload_to_dropbox.log_task_progress")
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_auth_error_handling(self, mock_settings, mock_log, mock_client, tmp_path):
|
|
"""Test AuthError during upload is caught and re-raised."""
|
|
from app.tasks.upload_to_dropbox import upload_to_dropbox
|
|
|
|
mock_settings.dropbox_app_key = "key"
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
mock_settings.dropbox_refresh_token = "token"
|
|
mock_settings.dropbox_folder = "/uploads"
|
|
mock_settings.workdir = str(tmp_path)
|
|
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_bytes(b"test content")
|
|
|
|
mock_client.side_effect = AuthError("req-id", "Invalid token")
|
|
|
|
with pytest.raises(Exception, match="Authentication failed"):
|
|
upload_to_dropbox.apply(args=[str(test_file)], kwargs={"file_id": 1}).get()
|
|
|
|
@patch("app.tasks.upload_to_dropbox.get_dropbox_client")
|
|
@patch("app.tasks.upload_to_dropbox.log_task_progress")
|
|
@patch("app.tasks.upload_to_dropbox.settings")
|
|
def test_api_error_handling(self, mock_settings, mock_log, mock_client, tmp_path):
|
|
"""Test ApiError during upload is caught and re-raised."""
|
|
from app.tasks.upload_to_dropbox import upload_to_dropbox
|
|
|
|
mock_settings.dropbox_app_key = "key"
|
|
mock_settings.dropbox_app_secret = "secret"
|
|
mock_settings.dropbox_refresh_token = "token"
|
|
mock_settings.dropbox_folder = "/uploads"
|
|
mock_settings.workdir = str(tmp_path)
|
|
|
|
test_file = tmp_path / "test.pdf"
|
|
test_file.write_bytes(b"test content")
|
|
|
|
mock_error = Mock()
|
|
mock_error.is_path.return_value = False
|
|
mock_client.side_effect = ApiError("req-id", mock_error, "user msg", "header")
|
|
|
|
with pytest.raises(Exception, match="Failed to upload"):
|
|
upload_to_dropbox.apply(args=[str(test_file)], kwargs={"file_id": 1}).get()
|