Refactor: migrate doc upload info used in chat (#14359)

### What problem does this PR solve?

Before migration: POST /v1/document/upload_info/
After migration: POST /api/v1/documentss/upload/

### Type of change

- [x] Refactoring
This commit is contained in:
Jack
2026-04-27 16:58:42 +08:00
committed by GitHub
parent c446c403de
commit 61a24a2c14
7 changed files with 165 additions and 116 deletions

View File

@@ -327,6 +327,53 @@ def upload_documents(auth, payload=None, files_path=None, *, filename_override=N
f.close()
def upload_info(auth, files_path=None, *, url=None):
"""
Call the /api/v1/documents/upload endpoint to get upload info.
This is used to get file metadata before actually uploading to a dataset.
Args:
auth: Authentication object
files_path: List of file paths to upload (optional)
url: URL to fetch file from (optional, can be used alone or with files_path to test mixed input rejection)
Returns:
Response JSON with upload info
"""
url_endpoint = f"{HOST_ADDRESS}/api/{VERSION}/documents/upload"
fields = []
file_objects = []
try:
if files_path:
for fp in files_path:
p = Path(fp)
f = p.open("rb")
fields.append(("file", (p.name, f)))
file_objects.append(f)
# Add url as query parameter if provided
if url:
url_endpoint = f"{url_endpoint}?url={url}"
# Handle empty fields (no files) - create empty MultipartEncoder
if not fields:
fields = [("empty", ("", ""))]
m = MultipartEncoder(fields=fields)
res = requests.post(
url=url_endpoint,
headers={"Content-Type": m.content_type},
auth=auth,
data=m,
)
return res.json()
finally:
for f in file_objects:
f.close()
def create_document(auth, payload=None, *, headers=HEADERS, data=None):
kb_id = payload.get("kb_id") if payload else None
request_payload = dict(payload or {})

View File

@@ -15,12 +15,12 @@
#
import asyncio
from pathlib import Path
import importlib.util
import sys
from types import ModuleType
import pytest
from test_common import upload_info
from configs import INVALID_API_TOKEN
from libs.auth import RAGFlowWebApiAuth
from utils.file_utils import create_txt_file
class _AwaitableValue:
@@ -61,81 +61,55 @@ def _run(coro):
return asyncio.run(coro)
def _load_document_app_module(monkeypatch):
repo_root = Path(__file__).resolve().parents[4]
common_mod = ModuleType("common")
common_mod.bulk_upload_documents = lambda *_args, **_kwargs: []
common_mod.delete_document = lambda *_args, **_kwargs: None
common_mod.list_documents = lambda *_args, **_kwargs: {"data": {"docs": []}}
monkeypatch.setitem(sys.modules, "common", common_mod)
module_path = repo_root / "test" / "testcases" / "test_web_api" / "test_document_app" / "conftest.py"
spec = importlib.util.spec_from_file_location("test_document_app_unit_conftest", module_path)
module = importlib.util.module_from_spec(spec)
sys.modules["test_document_app_unit_conftest"] = module
spec.loader.exec_module(module)
return module.document_app_module.__wrapped__(monkeypatch)
# ============================================================================
# End-to-End Tests
# ============================================================================
@pytest.mark.p2
def test_upload_info_rejects_mixed_inputs(monkeypatch):
module = _load_document_app_module(monkeypatch)
monkeypatch.setattr(module, "assert_url_is_safe", lambda url: ("example.com", "93.184.216.34"))
files = _DummyFiles({"file": [_DummyFile("a.txt")]})
monkeypatch.setattr(module, "request", _DummyRequest(files=files, args={"url": "https://example.com/a.txt"}))
class TestUploadInfoE2E:
"""End-to-end tests for the /api/v1/documents/upload endpoint"""
res = _run(module.upload_info())
assert res["code"] == module.RetCode.BAD_REQUEST
assert "not both" in res["message"]
def test_upload_info_requires_file_or_url_e2e(self, WebApiAuth):
"""Test that missing both file and url returns error"""
# Call without files and without url
res = upload_info(WebApiAuth)
assert res["code"] == 101, res
assert "Missing input" in res["message"] or "file" in res["message"].lower() or "url" in res["message"].lower()
def test_upload_info_rejects_mixed_inputs_e2e(self, WebApiAuth, tmp_path):
"""Test that providing both file and url returns error"""
# Create a file
fp = create_txt_file(tmp_path / "test.txt")
@pytest.mark.p2
def test_upload_info_requires_file_or_url(monkeypatch):
module = _load_document_app_module(monkeypatch)
monkeypatch.setattr(module, "request", _DummyRequest(files=_DummyFiles()))
# Call with both file and url - the API should reject this
res = upload_info(WebApiAuth, files_path=[fp], url="https://example.com/test.txt")
# The API should return an error when both file and url are provided
assert res["code"] == 101, res
assert "not both" in res["message"].lower() and "either" in res["message"].lower()
res = _run(module.upload_info())
assert res["code"] == module.RetCode.BAD_REQUEST
assert "Missing input" in res["message"]
def test_upload_info_supports_url_single_and_multiple_files_e2e(self, WebApiAuth, tmp_path):
"""Test that the endpoint supports URL, single file, and multiple files"""
# Test with URL
# Note: Using a real URL might fail if the URL is not accessible
# For E2E testing, we test with actual file uploads
# Test with single file
fp1 = create_txt_file(tmp_path / "single_file.txt")
res = upload_info(WebApiAuth, files_path=[fp1])
assert res["code"] == 0, res
assert "data" in res, res
@pytest.mark.p2
def test_upload_info_supports_url_single_and_multiple_files(monkeypatch):
module = _load_document_app_module(monkeypatch)
monkeypatch.setattr(module, "assert_url_is_safe", lambda url: ("example.com", "93.184.216.34"))
captured = []
# Test with multiple files
fp2 = create_txt_file(tmp_path / "file_a.txt")
fp3 = create_txt_file(tmp_path / "file_b.txt")
res = upload_info(WebApiAuth, files_path=[fp2, fp3])
assert res["code"] == 0, res
assert "data" in res, res
# Should return a list for multiple files
if isinstance(res["data"], list):
assert len(res["data"]) == 2, res
def fake_upload_info(user_id, file_obj, url=None):
captured.append((user_id, getattr(file_obj, "filename", None), url))
if url is not None:
return {"kind": "url", "value": url}
return {"kind": "file", "value": file_obj.filename}
monkeypatch.setattr(module.FileService, "upload_info", fake_upload_info)
monkeypatch.setattr(module, "request", _DummyRequest(files=_DummyFiles(), args={"url": "https://example.com/a.txt"}))
res = _run(module.upload_info())
assert res["code"] == 0
assert res["data"] == {"kind": "url", "value": "https://example.com/a.txt"}
monkeypatch.setattr(module, "request", _DummyRequest(files=_DummyFiles({"file": _DummyFile("single.txt")})))
res = _run(module.upload_info())
assert res["code"] == 0
assert res["data"] == {"kind": "file", "value": "single.txt"}
monkeypatch.setattr(
module,
"request",
_DummyRequest(files=_DummyFiles({"file": [_DummyFile("a.txt"), _DummyFile("b.txt")]})),
)
res = _run(module.upload_info())
assert res["code"] == 0
assert res["data"] == [
{"kind": "file", "value": "a.txt"},
{"kind": "file", "value": "b.txt"},
]
assert captured == [
("user-1", None, "https://example.com/a.txt"),
("user-1", "single.txt", None),
("user-1", "a.txt", None),
("user-1", "b.txt", None),
]
def test_upload_info_invalid_auth(self):
"""Test that invalid authentication returns error"""
res = upload_info(RAGFlowWebApiAuth(INVALID_API_TOKEN), files_path=[])
assert res["code"] == 401, res