195c3c3446
The per-user notification functions (notify_user_document_processed / notify_user_document_failed) were defined but never called from the document processing pipeline. - Call notify_user_document_processed in finalize_document_storage when owner_id is available (creates in-app + email/webhook notifications) - Add _dispatch_user_failure_notification helper to celery_app.py that extracts file_id from failed task args and dispatches notify_user_document_failed for document pipeline tasks - Add comprehensive tests for both success and failure notification paths Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
769 lines
37 KiB
Python
769 lines
37 KiB
Python
"""Comprehensive unit tests for app/tasks/finalize_document_storage.py module."""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from app.tasks.finalize_document_storage import finalize_document_storage
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Shared helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_file_record(file_id: int = 123, owner_id=None):
|
|
"""Return a lightweight MagicMock that mimics a FileRecord."""
|
|
rec = MagicMock()
|
|
rec.id = file_id
|
|
rec.owner_id = owner_id
|
|
return rec
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _patch_celery_background_tasks(mocker):
|
|
"""Module-level autouse fixture: prevent lazy-imported Celery tasks from connecting to Redis."""
|
|
mocker.patch("app.tasks.compute_embedding.compute_document_embedding")
|
|
mocker.patch("app.tasks.convert_to_pdfa.convert_to_pdfa", create=True)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestFinalizeDocumentStorage:
|
|
"""Tests for finalize_document_storage Celery task."""
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_successful_finalization(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""Test successful document finalization with all services configured."""
|
|
mock_get_services.return_value = {
|
|
"dropbox": True,
|
|
"google_drive": True,
|
|
"nextcloud": False,
|
|
"s3": True,
|
|
}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_file_record = _make_file_record(123, owner_id=None)
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_file_record
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=102400):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="test_document.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
metadata = {
|
|
"filename": "test_document.pdf",
|
|
"document_type": "Invoice",
|
|
"tags": ["invoice", "test"],
|
|
}
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/test_document.pdf",
|
|
metadata=metadata,
|
|
file_id=123,
|
|
)
|
|
|
|
# owner_id=None → global routing
|
|
mock_send_all.delay.assert_called_once_with("/workdir/processed/test_document.pdf", True, 123)
|
|
mock_send_user.delay.assert_not_called()
|
|
|
|
mock_notify.assert_called_once()
|
|
notify_args = mock_notify.call_args[1]
|
|
assert notify_args["filename"] == "test_document.pdf"
|
|
assert notify_args["file_size"] == 102400
|
|
assert notify_args["metadata"] == metadata
|
|
assert "Dropbox" in notify_args["destinations"]
|
|
assert "Google Drive" in notify_args["destinations"]
|
|
assert "S3" in notify_args["destinations"]
|
|
|
|
assert result["status"] == "Completed"
|
|
assert result["file"] == "/workdir/processed/test_document.pdf"
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_retrieves_file_id_from_database_when_not_provided(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""Test file_id retrieval from database when not provided."""
|
|
mock_get_services.return_value = {"dropbox": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_file_record = _make_file_record(456, owner_id=None)
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_file_record
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=50000):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="doc.pdf"):
|
|
with patch(
|
|
"app.tasks.finalize_document_storage.os.path.join", return_value="/tmp/tmp/original.pdf"
|
|
):
|
|
with patch("app.tasks.finalize_document_storage.settings") as mock_settings:
|
|
mock_settings.workdir = "/tmp"
|
|
mock_settings.enable_pdfa_conversion = False
|
|
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/doc.pdf",
|
|
metadata={"filename": "doc.pdf"},
|
|
file_id=None, # Not provided
|
|
)
|
|
|
|
# Verify database was queried
|
|
mock_db.query.assert_called_once()
|
|
|
|
# Verify send_to_all was called (global routing — no user destinations)
|
|
mock_send_all.delay.assert_called_once()
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_handles_no_configured_services(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""Test handles case when no services are configured."""
|
|
mock_get_services.return_value = {
|
|
"dropbox": False,
|
|
"google_drive": False,
|
|
"nextcloud": False,
|
|
"s3": False,
|
|
}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=1024):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="test.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/test.pdf",
|
|
metadata={"filename": "test.pdf"},
|
|
file_id=789,
|
|
)
|
|
|
|
# Should still queue global uploads (even if none configured)
|
|
mock_send_all.delay.assert_called_once()
|
|
|
|
mock_notify.assert_called_once()
|
|
notify_args = mock_notify.call_args[1]
|
|
assert notify_args["destinations"] == []
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_handles_get_configured_services_exception(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""Test handles exception when getting configured services."""
|
|
mock_get_services.side_effect = Exception("Service validation failed")
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=2048):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="file.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/file.pdf",
|
|
metadata={"filename": "file.pdf"},
|
|
file_id=101,
|
|
)
|
|
|
|
assert result["status"] == "Completed"
|
|
|
|
mock_notify.assert_called_once()
|
|
notify_args = mock_notify.call_args[1]
|
|
assert "configured destinations" in notify_args["destinations"]
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_handles_notification_failure(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""Test handles notification failure gracefully."""
|
|
mock_get_services.return_value = {"dropbox": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
mock_notify.side_effect = Exception("Notification service unavailable")
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=4096):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="doc.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/doc.pdf",
|
|
metadata={"filename": "doc.pdf"},
|
|
file_id=202,
|
|
)
|
|
|
|
assert result["status"] == "Completed"
|
|
mock_send_all.delay.assert_called_once()
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_handles_missing_file(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""Test handles case when processed file doesn't exist."""
|
|
mock_get_services.return_value = {"dropbox": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=False):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="missing.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/missing.pdf",
|
|
metadata={"filename": "missing.pdf"},
|
|
file_id=303,
|
|
)
|
|
|
|
mock_send_all.delay.assert_called_once()
|
|
|
|
mock_notify.assert_called_once()
|
|
notify_args = mock_notify.call_args[1]
|
|
assert notify_args["file_size"] == 0
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_formats_service_names_for_display(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""Test that service names are formatted correctly for display."""
|
|
mock_get_services.return_value = {
|
|
"google_drive": True,
|
|
"one_drive": True,
|
|
"next_cloud": False,
|
|
}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=8192):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="test.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/test.pdf",
|
|
metadata={"filename": "test.pdf"},
|
|
file_id=404,
|
|
)
|
|
|
|
mock_notify.assert_called_once()
|
|
notify_args = mock_notify.call_args[1]
|
|
destinations = notify_args["destinations"]
|
|
assert "Google Drive" in destinations
|
|
assert "One Drive" in destinations
|
|
assert "Next Cloud" not in destinations
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_passes_delete_after_flag_to_send_all(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""Test that delete_after flag is correctly passed to send_to_all_destinations."""
|
|
mock_get_services.return_value = {"s3": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=1024):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="file.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/file.pdf",
|
|
metadata={"filename": "file.pdf"},
|
|
file_id=505,
|
|
)
|
|
|
|
mock_send_all.delay.assert_called_once_with("/workdir/processed/file.pdf", True, 505)
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_pdfa_enabled_does_not_regress_finalize_step_to_in_progress(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""
|
|
Regression test: when PDF/A conversion is enabled, the finalize_document_storage
|
|
step must NOT be logged as in_progress after it has already been logged as success.
|
|
"""
|
|
mock_get_services.return_value = {"dropbox": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_db.query.return_value.filter.return_value.first.return_value = None
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=1024):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="doc.pdf"):
|
|
with patch("app.tasks.finalize_document_storage.settings") as mock_settings:
|
|
mock_settings.workdir = "/tmp"
|
|
mock_settings.enable_pdfa_conversion = True
|
|
|
|
mock_convert = MagicMock()
|
|
with patch(
|
|
"app.tasks.finalize_document_storage.convert_to_pdfa",
|
|
mock_convert,
|
|
create=True,
|
|
):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/doc.pdf",
|
|
metadata={"filename": "doc.pdf"},
|
|
file_id=606,
|
|
)
|
|
|
|
# Collect all logged calls for finalize_document_storage step
|
|
finalize_calls = [
|
|
c for c in mock_log_progress.call_args_list if c.args[1] == "finalize_document_storage"
|
|
]
|
|
|
|
statuses = [c.args[2] for c in finalize_calls]
|
|
assert "success" in statuses, "finalize_document_storage must be logged as success"
|
|
assert statuses[-1] == "success", (
|
|
"finalize_document_storage must not be regressed to in_progress after success; "
|
|
f"got statuses: {statuses}"
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestFinalizeDocumentStorageUserRouting:
|
|
"""Tests for user-specific destination routing in finalize_document_storage."""
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=2)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_routes_to_user_destinations_when_owner_has_integrations(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""When a user has active DESTINATION integrations, use them instead of global config."""
|
|
mock_get_services.return_value = {"dropbox": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_file_record = _make_file_record(100, owner_id="alice@example.com")
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_file_record
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=1024):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="doc.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/doc.pdf",
|
|
metadata={"filename": "doc.pdf"},
|
|
file_id=100,
|
|
)
|
|
|
|
mock_send_user.delay.assert_called_once_with("/workdir/processed/doc.pdf", "alice@example.com", 100)
|
|
mock_send_all.delay.assert_not_called()
|
|
assert result["status"] == "Completed"
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_falls_back_to_global_when_owner_has_no_integrations(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""When a user has no active DESTINATION integrations, fall back to global config."""
|
|
mock_get_services.return_value = {"s3": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_file_record = _make_file_record(200, owner_id="bob@example.com")
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_file_record
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=2048):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="file.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/file.pdf",
|
|
metadata={"filename": "file.pdf"},
|
|
file_id=200,
|
|
)
|
|
|
|
mock_send_all.delay.assert_called_once_with("/workdir/processed/file.pdf", True, 200)
|
|
mock_send_user.delay.assert_not_called()
|
|
assert result["status"] == "Completed"
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_falls_back_to_global_when_no_owner(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""When a document has no owner (single-user mode), global destinations are used."""
|
|
mock_get_services.return_value = {"nextcloud": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_file_record = _make_file_record(300, owner_id=None)
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_file_record
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=512):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="scan.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/scan.pdf",
|
|
metadata={"filename": "scan.pdf"},
|
|
file_id=300,
|
|
)
|
|
|
|
mock_send_all.delay.assert_called_once_with("/workdir/processed/scan.pdf", True, 300)
|
|
mock_send_user.delay.assert_not_called()
|
|
# get_user_destination_count must NOT be called when owner_id is None
|
|
mock_get_dest_count.assert_not_called()
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count")
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_falls_back_to_global_when_count_lookup_fails(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify,
|
|
):
|
|
"""When get_user_destination_count raises, fall back to global routing gracefully."""
|
|
mock_get_services.return_value = {"s3": True}
|
|
mock_get_dest_count.side_effect = Exception("DB connection error")
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_file_record = _make_file_record(400, owner_id="charlie@example.com")
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_file_record
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=4096):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="file.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/file.pdf",
|
|
metadata={"filename": "file.pdf"},
|
|
file_id=400,
|
|
)
|
|
|
|
mock_send_all.delay.assert_called_once_with("/workdir/processed/file.pdf", True, 400)
|
|
mock_send_user.delay.assert_not_called()
|
|
assert result["status"] == "Completed"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestFinalizeDocumentStorageUserNotification:
|
|
"""Tests for per-user notification dispatch in finalize_document_storage."""
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_user_document_processed")
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=2)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_dispatches_per_user_notification_when_owner_is_set(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify_system,
|
|
mock_notify_user,
|
|
):
|
|
"""notify_user_document_processed is called when owner_id is available."""
|
|
mock_get_services.return_value = {"dropbox": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_file_record = _make_file_record(100, owner_id="alice@example.com")
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_file_record
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=1024):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="doc.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/doc.pdf",
|
|
metadata={"filename": "doc.pdf"},
|
|
file_id=100,
|
|
)
|
|
|
|
mock_notify_user.assert_called_once_with(
|
|
owner_id="alice@example.com",
|
|
filename="doc.pdf",
|
|
file_id=100,
|
|
)
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_user_document_processed")
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_skips_per_user_notification_when_no_owner(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify_system,
|
|
mock_notify_user,
|
|
):
|
|
"""notify_user_document_processed is NOT called when owner_id is None."""
|
|
mock_get_services.return_value = {"dropbox": True}
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_file_record = _make_file_record(200, owner_id=None)
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_file_record
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=2048):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="file.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/file.pdf",
|
|
metadata={"filename": "file.pdf"},
|
|
file_id=200,
|
|
)
|
|
|
|
mock_notify_user.assert_not_called()
|
|
|
|
@patch("app.tasks.finalize_document_storage.notify_user_document_processed")
|
|
@patch("app.tasks.finalize_document_storage.notify_file_processed")
|
|
@patch("app.tasks.finalize_document_storage.send_to_user_destinations")
|
|
@patch("app.tasks.finalize_document_storage.send_to_all_destinations")
|
|
@patch("app.tasks.finalize_document_storage.get_user_destination_count", return_value=0)
|
|
@patch("app.tasks.finalize_document_storage.get_configured_services_from_validator")
|
|
@patch("app.tasks.finalize_document_storage.log_task_progress")
|
|
@patch("app.tasks.finalize_document_storage.SessionLocal")
|
|
def test_per_user_notification_failure_does_not_break_task(
|
|
self,
|
|
mock_session_local,
|
|
mock_log_progress,
|
|
mock_get_services,
|
|
mock_get_dest_count,
|
|
mock_send_all,
|
|
mock_send_user,
|
|
mock_notify_system,
|
|
mock_notify_user,
|
|
):
|
|
"""Even if notify_user_document_processed raises, finalize returns success."""
|
|
mock_get_services.return_value = {"dropbox": True}
|
|
mock_notify_user.side_effect = RuntimeError("SMTP down")
|
|
|
|
mock_db = MagicMock()
|
|
mock_session_local.return_value.__enter__.return_value = mock_db
|
|
mock_file_record = _make_file_record(300, owner_id="bob@example.com")
|
|
mock_db.query.return_value.filter.return_value.first.return_value = mock_file_record
|
|
|
|
with patch("app.tasks.finalize_document_storage.os.path.exists", return_value=True):
|
|
with patch("app.tasks.finalize_document_storage.os.path.getsize", return_value=512):
|
|
with patch("app.tasks.finalize_document_storage.os.path.basename", return_value="a.pdf"):
|
|
finalize_document_storage.request.id = "test-task-id"
|
|
|
|
result = finalize_document_storage.__wrapped__(
|
|
original_file="/tmp/original.pdf",
|
|
processed_file="/workdir/processed/a.pdf",
|
|
metadata={"filename": "a.pdf"},
|
|
file_id=300,
|
|
)
|
|
|
|
assert result["status"] == "Completed"
|
|
mock_notify_user.assert_called_once()
|