mirror of https://github.com/langgenius/dify.git
fix: fix use fastopenapi lead user is anonymouse (#32236)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
5b4c7b2a40
commit
2f87ecc0ce
|
|
@ -1,6 +1,7 @@
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from flask_restx import Resource
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
import services
|
import services
|
||||||
|
|
@ -10,12 +11,12 @@ from controllers.common.errors import (
|
||||||
RemoteFileUploadError,
|
RemoteFileUploadError,
|
||||||
UnsupportedFileTypeError,
|
UnsupportedFileTypeError,
|
||||||
)
|
)
|
||||||
from controllers.fastopenapi import console_router
|
from controllers.console import console_ns
|
||||||
from core.file import helpers as file_helpers
|
from core.file import helpers as file_helpers
|
||||||
from core.helper import ssrf_proxy
|
from core.helper import ssrf_proxy
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from fields.file_fields import FileWithSignedUrl, RemoteFileInfo
|
from fields.file_fields import FileWithSignedUrl, RemoteFileInfo
|
||||||
from libs.login import current_account_with_tenant
|
from libs.login import current_account_with_tenant, login_required
|
||||||
from services.file_service import FileService
|
from services.file_service import FileService
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,69 +24,73 @@ class RemoteFileUploadPayload(BaseModel):
|
||||||
url: str = Field(..., description="URL to fetch")
|
url: str = Field(..., description="URL to fetch")
|
||||||
|
|
||||||
|
|
||||||
@console_router.get(
|
@console_ns.route("/remote-files/<path:url>")
|
||||||
"/remote-files/<path:url>",
|
class GetRemoteFileInfo(Resource):
|
||||||
response_model=RemoteFileInfo,
|
@login_required
|
||||||
tags=["console"],
|
def get(self, url: str):
|
||||||
)
|
decoded_url = urllib.parse.unquote(url)
|
||||||
def get_remote_file_info(url: str) -> RemoteFileInfo:
|
resp = ssrf_proxy.head(decoded_url)
|
||||||
decoded_url = urllib.parse.unquote(url)
|
|
||||||
resp = ssrf_proxy.head(decoded_url)
|
|
||||||
if resp.status_code != httpx.codes.OK:
|
|
||||||
resp = ssrf_proxy.get(decoded_url, timeout=3)
|
|
||||||
resp.raise_for_status()
|
|
||||||
return RemoteFileInfo(
|
|
||||||
file_type=resp.headers.get("Content-Type", "application/octet-stream"),
|
|
||||||
file_length=int(resp.headers.get("Content-Length", 0)),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@console_router.post(
|
|
||||||
"/remote-files/upload",
|
|
||||||
response_model=FileWithSignedUrl,
|
|
||||||
tags=["console"],
|
|
||||||
status_code=201,
|
|
||||||
)
|
|
||||||
def upload_remote_file(payload: RemoteFileUploadPayload) -> FileWithSignedUrl:
|
|
||||||
url = payload.url
|
|
||||||
|
|
||||||
try:
|
|
||||||
resp = ssrf_proxy.head(url=url)
|
|
||||||
if resp.status_code != httpx.codes.OK:
|
if resp.status_code != httpx.codes.OK:
|
||||||
resp = ssrf_proxy.get(url=url, timeout=3, follow_redirects=True)
|
resp = ssrf_proxy.get(decoded_url, timeout=3)
|
||||||
if resp.status_code != httpx.codes.OK:
|
resp.raise_for_status()
|
||||||
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {resp.text}")
|
return RemoteFileInfo(
|
||||||
except httpx.RequestError as e:
|
file_type=resp.headers.get("Content-Type", "application/octet-stream"),
|
||||||
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {str(e)}")
|
file_length=int(resp.headers.get("Content-Length", 0)),
|
||||||
|
).model_dump(mode="json")
|
||||||
|
|
||||||
file_info = helpers.guess_file_info_from_response(resp)
|
|
||||||
|
|
||||||
if not FileService.is_file_size_within_limit(extension=file_info.extension, file_size=file_info.size):
|
@console_ns.route("/remote-files/upload")
|
||||||
raise FileTooLargeError
|
class RemoteFileUpload(Resource):
|
||||||
|
@login_required
|
||||||
|
def post(self):
|
||||||
|
payload = RemoteFileUploadPayload.model_validate(console_ns.payload)
|
||||||
|
url = payload.url
|
||||||
|
|
||||||
content = resp.content if resp.request.method == "GET" else ssrf_proxy.get(url).content
|
# Try to fetch remote file metadata/content first
|
||||||
|
try:
|
||||||
|
resp = ssrf_proxy.head(url=url)
|
||||||
|
if resp.status_code != httpx.codes.OK:
|
||||||
|
resp = ssrf_proxy.get(url=url, timeout=3, follow_redirects=True)
|
||||||
|
if resp.status_code != httpx.codes.OK:
|
||||||
|
# Normalize into a user-friendly error message expected by tests
|
||||||
|
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {resp.text}")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
raise RemoteFileUploadError(f"Failed to fetch file from {url}: {str(e)}")
|
||||||
|
|
||||||
try:
|
file_info = helpers.guess_file_info_from_response(resp)
|
||||||
user, _ = current_account_with_tenant()
|
|
||||||
upload_file = FileService(db.engine).upload_file(
|
# Enforce file size limit with 400 (Bad Request) per tests' expectation
|
||||||
filename=file_info.filename,
|
if not FileService.is_file_size_within_limit(extension=file_info.extension, file_size=file_info.size):
|
||||||
content=content,
|
raise FileTooLargeError()
|
||||||
mimetype=file_info.mimetype,
|
|
||||||
user=user,
|
# Load content if needed
|
||||||
source_url=url,
|
content = resp.content if resp.request.method == "GET" else ssrf_proxy.get(url).content
|
||||||
|
|
||||||
|
try:
|
||||||
|
user, _ = current_account_with_tenant()
|
||||||
|
upload_file = FileService(db.engine).upload_file(
|
||||||
|
filename=file_info.filename,
|
||||||
|
content=content,
|
||||||
|
mimetype=file_info.mimetype,
|
||||||
|
user=user,
|
||||||
|
source_url=url,
|
||||||
|
)
|
||||||
|
except services.errors.file.FileTooLargeError as file_too_large_error:
|
||||||
|
raise FileTooLargeError(file_too_large_error.description)
|
||||||
|
except services.errors.file.UnsupportedFileTypeError:
|
||||||
|
raise UnsupportedFileTypeError()
|
||||||
|
|
||||||
|
# Success: return created resource with 201 status
|
||||||
|
return (
|
||||||
|
FileWithSignedUrl(
|
||||||
|
id=upload_file.id,
|
||||||
|
name=upload_file.name,
|
||||||
|
size=upload_file.size,
|
||||||
|
extension=upload_file.extension,
|
||||||
|
url=file_helpers.get_signed_file_url(upload_file_id=upload_file.id),
|
||||||
|
mime_type=upload_file.mime_type,
|
||||||
|
created_by=upload_file.created_by,
|
||||||
|
created_at=int(upload_file.created_at.timestamp()),
|
||||||
|
).model_dump(mode="json"),
|
||||||
|
201,
|
||||||
)
|
)
|
||||||
except services.errors.file.FileTooLargeError as file_too_large_error:
|
|
||||||
raise FileTooLargeError(file_too_large_error.description)
|
|
||||||
except services.errors.file.UnsupportedFileTypeError:
|
|
||||||
raise UnsupportedFileTypeError()
|
|
||||||
|
|
||||||
return FileWithSignedUrl(
|
|
||||||
id=upload_file.id,
|
|
||||||
name=upload_file.name,
|
|
||||||
size=upload_file.size,
|
|
||||||
extension=upload_file.extension,
|
|
||||||
url=file_helpers.get_signed_file_url(upload_file_id=upload_file.id),
|
|
||||||
mime_type=upload_file.mime_type,
|
|
||||||
created_by=upload_file.created_by,
|
|
||||||
created_at=int(upload_file.created_at.timestamp()),
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,286 @@
|
||||||
import builtins
|
"""Tests for remote file upload API endpoints using Flask-RESTX."""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from unittest.mock import patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
from flask import Flask
|
from flask import Flask, g
|
||||||
from flask.views import MethodView
|
|
||||||
|
|
||||||
from extensions import ext_fastopenapi
|
|
||||||
|
|
||||||
if not hasattr(builtins, "MethodView"):
|
|
||||||
builtins.MethodView = MethodView # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def app() -> Flask:
|
def app() -> Flask:
|
||||||
|
"""Create Flask app for testing."""
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config["TESTING"] = True
|
app.config["TESTING"] = True
|
||||||
|
app.config["SECRET_KEY"] = "test-secret-key"
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
def test_console_remote_files_fastopenapi_get_info(app: Flask):
|
@pytest.fixture
|
||||||
ext_fastopenapi.init_app(app)
|
def client(app):
|
||||||
|
"""Create test client with console blueprint registered."""
|
||||||
|
from controllers.console import bp
|
||||||
|
|
||||||
response = httpx.Response(
|
app.register_blueprint(bp)
|
||||||
200,
|
return app.test_client()
|
||||||
request=httpx.Request("HEAD", "http://example.com/file.txt"),
|
|
||||||
headers={"Content-Type": "text/plain", "Content-Length": "10"},
|
|
||||||
)
|
|
||||||
|
|
||||||
with patch("controllers.console.remote_files.ssrf_proxy.head", return_value=response):
|
|
||||||
client = app.test_client()
|
|
||||||
encoded_url = "http%3A%2F%2Fexample.com%2Ffile.txt"
|
|
||||||
resp = client.get(f"/console/api/remote-files/{encoded_url}")
|
|
||||||
|
|
||||||
assert resp.status_code == 200
|
|
||||||
assert resp.get_json() == {"file_type": "text/plain", "file_length": 10}
|
|
||||||
|
|
||||||
|
|
||||||
def test_console_remote_files_fastopenapi_upload(app: Flask):
|
@pytest.fixture
|
||||||
ext_fastopenapi.init_app(app)
|
def mock_account():
|
||||||
|
"""Create a mock account for testing."""
|
||||||
|
from models import Account
|
||||||
|
|
||||||
head_response = httpx.Response(
|
account = Mock(spec=Account)
|
||||||
200,
|
account.id = "test-account-id"
|
||||||
request=httpx.Request("GET", "http://example.com/file.txt"),
|
account.current_tenant_id = "test-tenant-id"
|
||||||
content=b"hello",
|
return account
|
||||||
)
|
|
||||||
file_info = SimpleNamespace(
|
|
||||||
extension="txt",
|
|
||||||
size=5,
|
|
||||||
filename="file.txt",
|
|
||||||
mimetype="text/plain",
|
|
||||||
)
|
|
||||||
uploaded = SimpleNamespace(
|
|
||||||
id="file-id",
|
|
||||||
name="file.txt",
|
|
||||||
size=5,
|
|
||||||
extension="txt",
|
|
||||||
mime_type="text/plain",
|
|
||||||
created_by="user-id",
|
|
||||||
created_at=datetime(2024, 1, 1),
|
|
||||||
)
|
|
||||||
|
|
||||||
with (
|
|
||||||
patch("controllers.console.remote_files.db", new=SimpleNamespace(engine=object())),
|
@pytest.fixture
|
||||||
patch("controllers.console.remote_files.ssrf_proxy.head", return_value=head_response),
|
def auth_ctx(app, mock_account):
|
||||||
patch("controllers.console.remote_files.helpers.guess_file_info_from_response", return_value=file_info),
|
"""Context manager to set auth/tenant context in flask.g for a request."""
|
||||||
patch("controllers.console.remote_files.FileService.is_file_size_within_limit", return_value=True),
|
|
||||||
patch("controllers.console.remote_files.FileService.__init__", return_value=None),
|
@contextlib.contextmanager
|
||||||
patch("controllers.console.remote_files.current_account_with_tenant", return_value=(object(), "tenant-id")),
|
def _ctx():
|
||||||
patch("controllers.console.remote_files.FileService.upload_file", return_value=uploaded),
|
with app.test_request_context():
|
||||||
patch("controllers.console.remote_files.file_helpers.get_signed_file_url", return_value="signed-url"),
|
g._login_user = mock_account
|
||||||
):
|
g._current_tenant = mock_account.current_tenant_id
|
||||||
client = app.test_client()
|
yield
|
||||||
resp = client.post(
|
|
||||||
"/console/api/remote-files/upload",
|
return _ctx
|
||||||
json={"url": "http://example.com/file.txt"},
|
|
||||||
|
|
||||||
|
class TestGetRemoteFileInfo:
|
||||||
|
"""Test GET /console/api/remote-files/<path:url> endpoint."""
|
||||||
|
|
||||||
|
def test_get_remote_file_info_success(self, app, client, mock_account):
|
||||||
|
"""Test successful retrieval of remote file info."""
|
||||||
|
response = httpx.Response(
|
||||||
|
200,
|
||||||
|
request=httpx.Request("HEAD", "http://example.com/file.txt"),
|
||||||
|
headers={"Content-Type": "text/plain", "Content-Length": "1024"},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert resp.status_code == 201
|
with (
|
||||||
assert resp.get_json() == {
|
patch(
|
||||||
"id": "file-id",
|
"controllers.console.remote_files.current_account_with_tenant",
|
||||||
"name": "file.txt",
|
return_value=(mock_account, "test-tenant-id"),
|
||||||
"size": 5,
|
),
|
||||||
"extension": "txt",
|
patch("controllers.console.remote_files.ssrf_proxy.head", return_value=response),
|
||||||
"url": "signed-url",
|
patch("libs.login.check_csrf_token", return_value=None),
|
||||||
"mime_type": "text/plain",
|
):
|
||||||
"created_by": "user-id",
|
with app.test_request_context():
|
||||||
"created_at": int(uploaded.created_at.timestamp()),
|
g._login_user = mock_account
|
||||||
}
|
g._current_tenant = mock_account.current_tenant_id
|
||||||
|
encoded_url = "http%3A%2F%2Fexample.com%2Ffile.txt"
|
||||||
|
resp = client.get(f"/console/api/remote-files/{encoded_url}")
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert data["file_type"] == "text/plain"
|
||||||
|
assert data["file_length"] == 1024
|
||||||
|
|
||||||
|
def test_get_remote_file_info_fallback_to_get_on_head_failure(self, app, client, mock_account):
|
||||||
|
"""Test fallback to GET when HEAD returns non-200 status."""
|
||||||
|
head_response = httpx.Response(
|
||||||
|
404,
|
||||||
|
request=httpx.Request("HEAD", "http://example.com/file.pdf"),
|
||||||
|
)
|
||||||
|
get_response = httpx.Response(
|
||||||
|
200,
|
||||||
|
request=httpx.Request("GET", "http://example.com/file.pdf"),
|
||||||
|
headers={"Content-Type": "application/pdf", "Content-Length": "2048"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.current_account_with_tenant",
|
||||||
|
return_value=(mock_account, "test-tenant-id"),
|
||||||
|
),
|
||||||
|
patch("controllers.console.remote_files.ssrf_proxy.head", return_value=head_response),
|
||||||
|
patch("controllers.console.remote_files.ssrf_proxy.get", return_value=get_response),
|
||||||
|
patch("libs.login.check_csrf_token", return_value=None),
|
||||||
|
):
|
||||||
|
with app.test_request_context():
|
||||||
|
g._login_user = mock_account
|
||||||
|
g._current_tenant = mock_account.current_tenant_id
|
||||||
|
encoded_url = "http%3A%2F%2Fexample.com%2Ffile.pdf"
|
||||||
|
resp = client.get(f"/console/api/remote-files/{encoded_url}")
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
data = resp.get_json()
|
||||||
|
assert data["file_type"] == "application/pdf"
|
||||||
|
assert data["file_length"] == 2048
|
||||||
|
|
||||||
|
|
||||||
|
class TestRemoteFileUpload:
|
||||||
|
"""Test POST /console/api/remote-files/upload endpoint."""
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("head_status", "use_get"),
|
||||||
|
[
|
||||||
|
(200, False), # HEAD succeeds
|
||||||
|
(405, True), # HEAD fails -> fallback GET
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_upload_remote_file_success_paths(self, client, mock_account, auth_ctx, head_status, use_get):
|
||||||
|
url = "http://example.com/file.pdf"
|
||||||
|
head_resp = httpx.Response(
|
||||||
|
head_status,
|
||||||
|
request=httpx.Request("HEAD", url),
|
||||||
|
headers={"Content-Type": "application/pdf", "Content-Length": "1024"},
|
||||||
|
)
|
||||||
|
get_resp = httpx.Response(
|
||||||
|
200,
|
||||||
|
request=httpx.Request("GET", url),
|
||||||
|
headers={"Content-Type": "application/pdf", "Content-Length": "1024"},
|
||||||
|
content=b"file content",
|
||||||
|
)
|
||||||
|
|
||||||
|
file_info = SimpleNamespace(
|
||||||
|
extension="pdf",
|
||||||
|
size=1024,
|
||||||
|
filename="file.pdf",
|
||||||
|
mimetype="application/pdf",
|
||||||
|
)
|
||||||
|
uploaded_file = SimpleNamespace(
|
||||||
|
id="uploaded-file-id",
|
||||||
|
name="file.pdf",
|
||||||
|
size=1024,
|
||||||
|
extension="pdf",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
created_by="test-account-id",
|
||||||
|
created_at=datetime(2024, 1, 1, 12, 0, 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.current_account_with_tenant",
|
||||||
|
return_value=(mock_account, "test-tenant-id"),
|
||||||
|
),
|
||||||
|
patch("controllers.console.remote_files.ssrf_proxy.head", return_value=head_resp) as p_head,
|
||||||
|
patch("controllers.console.remote_files.ssrf_proxy.get", return_value=get_resp) as p_get,
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.helpers.guess_file_info_from_response",
|
||||||
|
return_value=file_info,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.FileService.is_file_size_within_limit",
|
||||||
|
return_value=True,
|
||||||
|
),
|
||||||
|
patch("controllers.console.remote_files.db", spec=["engine"]),
|
||||||
|
patch("controllers.console.remote_files.FileService") as mock_file_service,
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.file_helpers.get_signed_file_url",
|
||||||
|
return_value="http://example.com/signed-url",
|
||||||
|
),
|
||||||
|
patch("libs.login.check_csrf_token", return_value=None),
|
||||||
|
):
|
||||||
|
mock_file_service.return_value.upload_file.return_value = uploaded_file
|
||||||
|
|
||||||
|
with auth_ctx():
|
||||||
|
resp = client.post(
|
||||||
|
"/console/api/remote-files/upload",
|
||||||
|
json={"url": url},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status_code == 201
|
||||||
|
p_head.assert_called_once()
|
||||||
|
# GET is used either for fallback (HEAD fails) or to fetch content after HEAD succeeds
|
||||||
|
p_get.assert_called_once()
|
||||||
|
mock_file_service.return_value.upload_file.assert_called_once()
|
||||||
|
|
||||||
|
data = resp.get_json()
|
||||||
|
assert data["id"] == "uploaded-file-id"
|
||||||
|
assert data["name"] == "file.pdf"
|
||||||
|
assert data["size"] == 1024
|
||||||
|
assert data["extension"] == "pdf"
|
||||||
|
assert data["url"] == "http://example.com/signed-url"
|
||||||
|
assert data["mime_type"] == "application/pdf"
|
||||||
|
assert data["created_by"] == "test-account-id"
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("size_ok", "raises", "expected_status", "expected_msg"),
|
||||||
|
[
|
||||||
|
# When size check fails in controller, API returns 413 with message "File size exceeded..."
|
||||||
|
(False, None, 413, "file size exceeded"),
|
||||||
|
# When service raises unsupported type, controller maps to 415 with message "File type not allowed."
|
||||||
|
(True, "unsupported", 415, "file type not allowed"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_upload_remote_file_errors(
|
||||||
|
self, client, mock_account, auth_ctx, size_ok, raises, expected_status, expected_msg
|
||||||
|
):
|
||||||
|
url = "http://example.com/x.pdf"
|
||||||
|
head_resp = httpx.Response(
|
||||||
|
200,
|
||||||
|
request=httpx.Request("HEAD", url),
|
||||||
|
headers={"Content-Type": "application/pdf", "Content-Length": "9"},
|
||||||
|
)
|
||||||
|
file_info = SimpleNamespace(extension="pdf", size=9, filename="x.pdf", mimetype="application/pdf")
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.current_account_with_tenant",
|
||||||
|
return_value=(mock_account, "test-tenant-id"),
|
||||||
|
),
|
||||||
|
patch("controllers.console.remote_files.ssrf_proxy.head", return_value=head_resp),
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.helpers.guess_file_info_from_response",
|
||||||
|
return_value=file_info,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.FileService.is_file_size_within_limit",
|
||||||
|
return_value=size_ok,
|
||||||
|
),
|
||||||
|
patch("controllers.console.remote_files.db", spec=["engine"]),
|
||||||
|
patch("libs.login.check_csrf_token", return_value=None),
|
||||||
|
):
|
||||||
|
if raises == "unsupported":
|
||||||
|
from services.errors.file import UnsupportedFileTypeError
|
||||||
|
|
||||||
|
with patch("controllers.console.remote_files.FileService") as mock_file_service:
|
||||||
|
mock_file_service.return_value.upload_file.side_effect = UnsupportedFileTypeError("bad")
|
||||||
|
with auth_ctx():
|
||||||
|
resp = client.post(
|
||||||
|
"/console/api/remote-files/upload",
|
||||||
|
json={"url": url},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
with auth_ctx():
|
||||||
|
resp = client.post(
|
||||||
|
"/console/api/remote-files/upload",
|
||||||
|
json={"url": url},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status_code == expected_status
|
||||||
|
data = resp.get_json()
|
||||||
|
msg = (data.get("error") or {}).get("message") or data.get("message", "")
|
||||||
|
assert expected_msg in msg.lower()
|
||||||
|
|
||||||
|
def test_upload_remote_file_fetch_failure(self, client, mock_account, auth_ctx):
|
||||||
|
"""Test upload when fetching of remote file fails."""
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.current_account_with_tenant",
|
||||||
|
return_value=(mock_account, "test-tenant-id"),
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"controllers.console.remote_files.ssrf_proxy.head",
|
||||||
|
side_effect=httpx.RequestError("Connection failed"),
|
||||||
|
),
|
||||||
|
patch("libs.login.check_csrf_token", return_value=None),
|
||||||
|
):
|
||||||
|
with auth_ctx():
|
||||||
|
resp = client.post(
|
||||||
|
"/console/api/remote-files/upload",
|
||||||
|
json={"url": "http://unreachable.com/file.pdf"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status_code == 400
|
||||||
|
data = resp.get_json()
|
||||||
|
msg = (data.get("error") or {}).get("message") or data.get("message", "")
|
||||||
|
assert "failed to fetch" in msg.lower()
|
||||||
|
|
|
||||||
|
|
@ -496,6 +496,9 @@ class TestSchemaResolverClass:
|
||||||
avg_time_no_cache = sum(results1) / len(results1)
|
avg_time_no_cache = sum(results1) / len(results1)
|
||||||
|
|
||||||
# Second run (with cache) - run multiple times
|
# Second run (with cache) - run multiple times
|
||||||
|
# Warm up cache first
|
||||||
|
resolve_dify_schema_refs(schema)
|
||||||
|
|
||||||
results2 = []
|
results2 = []
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue