from contextlib import nullcontext from unittest.mock import MagicMock, patch import pytest from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom from core.app.file_access import FileAccessScope from services.file_request_service import FileRequestService @pytest.mark.parametrize( ("user_from", "invoke_from", "expected_user_from", "expected_invoke_from"), [ (UserFrom.ACCOUNT, InvokeFrom.DEBUGGER, UserFrom.ACCOUNT, InvokeFrom.DEBUGGER), ("end-user", "service-api", UserFrom.END_USER, InvokeFrom.SERVICE_API), ], ) def test_request_download_url_builds_file_under_bound_scope( user_from: UserFrom | str, invoke_from: InvokeFrom | str, expected_user_from: UserFrom, expected_invoke_from: InvokeFrom, ) -> None: fake_file = MagicMock(filename="report.pdf", mime_type="application/pdf", size=123) access_controller = MagicMock() service = FileRequestService(access_controller=access_controller) reference = "dify-file-ref:tool-file-1" with ( patch("services.file_request_service.bind_file_access_scope", return_value=nullcontext()) as bind_scope, patch.object(service, "_build_file", return_value=fake_file) as build_file, patch( "services.file_request_service.file_helpers.resolve_file_url", return_value="https://files.example.com/x", ) as resolve_file_url, ): result = service.request_download_url( tenant_id="tenant-1", user_id="user-1", user_from=user_from, invoke_from=invoke_from, file_mapping={"transfer_method": "tool_file", "reference": reference}, ) bind_scope.assert_called_once() bound_scope = bind_scope.call_args.args[0] assert isinstance(bound_scope, FileAccessScope) assert bound_scope.tenant_id == "tenant-1" assert bound_scope.user_id == "user-1" assert bound_scope.user_from == expected_user_from assert bound_scope.invoke_from == expected_invoke_from build_file.assert_called_once_with( mapping={"transfer_method": "tool_file", "reference": reference}, tenant_id="tenant-1" ) resolve_file_url.assert_called_once_with(fake_file, for_external=True) assert result.filename == "report.pdf" assert result.mime_type == "application/pdf" assert result.size == 123 assert result.download_url == "https://files.example.com/x" def test_request_download_url_supports_internal_download_urls() -> None: fake_file = MagicMock(filename="report.pdf", mime_type="application/pdf", size=123) service = FileRequestService(access_controller=MagicMock()) with ( patch("services.file_request_service.bind_file_access_scope", return_value=nullcontext()), patch.object(service, "_build_file", return_value=fake_file), patch( "services.file_request_service.file_helpers.resolve_file_url", return_value="http://internal-files/report.pdf", ) as resolve_file_url, ): result = service.request_download_url( tenant_id="tenant-1", user_id="user-1", user_from="account", invoke_from="debugger", file_mapping={"transfer_method": "tool_file", "reference": "dify-file-ref:tool-file-1"}, for_external=False, ) resolve_file_url.assert_called_once_with(fake_file, for_external=False) assert result.download_url == "http://internal-files/report.pdf" def test_request_download_url_rejects_unsupported_files() -> None: service = FileRequestService(access_controller=MagicMock()) with ( patch("services.file_request_service.bind_file_access_scope", return_value=nullcontext()), patch.object(service, "_build_file", return_value=MagicMock(filename="report.pdf", mime_type=None, size=1)), patch("services.file_request_service.file_helpers.resolve_file_url", return_value=None), ): with pytest.raises(ValueError, match="file does not support signed download"): service.request_download_url( tenant_id="tenant-1", user_id="user-1", user_from="account", invoke_from="debugger", file_mapping={"transfer_method": "unknown"}, )