201 lines
6.2 KiB
Python
201 lines
6.2 KiB
Python
"""Integration tests for the file management API."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from agenteval.config import get_settings
|
|
from agenteval.web.app import app
|
|
from agenteval.web.deps import get_db
|
|
from fastapi.testclient import TestClient
|
|
from sqlmodel import Session, SQLModel, create_engine
|
|
|
|
|
|
@pytest.fixture()
|
|
def files_client(tmp_path: Path, monkeypatch):
|
|
from agenteval.storage.db import ( # noqa: F401
|
|
EvalResultDB,
|
|
EvalRunDB,
|
|
EvalTargetDB,
|
|
FileCategoryDB,
|
|
FileRecordDB,
|
|
ScenarioDB,
|
|
TurnDB,
|
|
)
|
|
from agenteval.web.routers import files as files_module
|
|
|
|
engine = create_engine(
|
|
f"sqlite:///{tmp_path / 'files_api.db'}",
|
|
connect_args={"check_same_thread": False},
|
|
)
|
|
SQLModel.metadata.create_all(engine)
|
|
session = Session(engine)
|
|
files_dir = tmp_path / "files"
|
|
files_dir.mkdir()
|
|
|
|
monkeypatch.setattr(files_module, "FILES_DIR", files_dir)
|
|
settings = get_settings()
|
|
monkeypatch.setattr(settings, "allowed_extensions", "txt,json")
|
|
monkeypatch.setattr(settings, "max_upload_size_mb", 1)
|
|
|
|
def override_get_db():
|
|
yield session
|
|
|
|
app.dependency_overrides[get_db] = override_get_db
|
|
with TestClient(app) as client:
|
|
yield client, session, files_dir
|
|
|
|
app.dependency_overrides.clear()
|
|
session.close()
|
|
engine.dispose()
|
|
|
|
|
|
def _create_category(client: TestClient, name: str, parent_id: str | None = None) -> dict:
|
|
response = client.post(
|
|
"/api/files/categories",
|
|
json={"name": name, "parent_id": parent_id},
|
|
)
|
|
assert response.status_code == 200
|
|
return response.json()
|
|
|
|
|
|
def test_upload_config_uses_server_settings(files_client):
|
|
client, _, _ = files_client
|
|
|
|
response = client.get("/api/files/config")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json() == {
|
|
"allowed_extensions": ["json", "txt"],
|
|
"max_upload_size_mb": 1,
|
|
}
|
|
|
|
|
|
def test_category_crud_returns_domain_fields_and_deep_tree(files_client):
|
|
client, _, _ = files_client
|
|
root = _create_category(client, "根分类")
|
|
child = _create_category(client, "二级", root["id"])
|
|
leaf = _create_category(client, "三级", child["id"])
|
|
|
|
tree = client.get("/api/files/categories").json()
|
|
|
|
assert tree[0]["id"] == root["id"]
|
|
assert tree[0]["name"] == "根分类"
|
|
assert tree[0]["children"][0]["id"] == child["id"]
|
|
assert tree[0]["children"][0]["children"][0]["id"] == leaf["id"]
|
|
|
|
updated = client.put(
|
|
f"/api/files/categories/{leaf['id']}",
|
|
json={"name": "三级分类"},
|
|
)
|
|
assert updated.status_code == 200
|
|
assert updated.json()["name"] == "三级分类"
|
|
|
|
|
|
def test_create_category_rejects_missing_parent(files_client):
|
|
client, _, _ = files_client
|
|
|
|
response = client.post(
|
|
"/api/files/categories",
|
|
json={"name": "孤立分类", "parent_id": "missing"},
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
assert response.json()["detail"] == "父分类不存在"
|
|
|
|
|
|
def test_list_files_includes_all_descendant_categories(files_client):
|
|
client, _, _ = files_client
|
|
root = _create_category(client, "根分类")
|
|
child = _create_category(client, "二级", root["id"])
|
|
leaf = _create_category(client, "三级", child["id"])
|
|
|
|
upload = client.post(
|
|
"/api/files/upload",
|
|
data={"category_id": leaf["id"]},
|
|
files={"file": ("deep.txt", b"deep content", "text/plain")},
|
|
)
|
|
assert upload.status_code == 200
|
|
|
|
response = client.get("/api/files", params={"category_id": root["id"]})
|
|
|
|
assert response.status_code == 200
|
|
assert [item["original_name"] for item in response.json()] == ["deep.txt"]
|
|
|
|
|
|
def test_upload_download_and_delete_file(files_client):
|
|
client, _, files_dir = files_client
|
|
|
|
uploaded = client.post(
|
|
"/api/files/upload",
|
|
files={"file": ("notes.txt", b"hello", "text/plain")},
|
|
)
|
|
|
|
assert uploaded.status_code == 200
|
|
record = uploaded.json()
|
|
assert record["original_name"] == "notes.txt"
|
|
assert record["file_size"] == 5
|
|
assert record["storage_directory"] == "data/files"
|
|
assert client.get("/api/files").json()[0]["storage_directory"] == "data/files"
|
|
assert len(list(files_dir.iterdir())) == 1
|
|
|
|
downloaded = client.get(f"/api/files/{record['id']}/download")
|
|
assert downloaded.status_code == 200
|
|
assert downloaded.content == b"hello"
|
|
|
|
deleted = client.delete(f"/api/files/{record['id']}")
|
|
assert deleted.status_code == 200
|
|
assert list(files_dir.iterdir()) == []
|
|
assert client.get("/api/files").json() == []
|
|
|
|
|
|
def test_upload_rejects_invalid_category_and_extension(files_client):
|
|
client, _, files_dir = files_client
|
|
|
|
invalid_category = client.post(
|
|
"/api/files/upload",
|
|
data={"category_id": "missing"},
|
|
files={"file": ("notes.txt", b"hello", "text/plain")},
|
|
)
|
|
invalid_extension = client.post(
|
|
"/api/files/upload",
|
|
files={"file": ("notes.exe", b"hello", "application/octet-stream")},
|
|
)
|
|
|
|
assert invalid_category.status_code == 400
|
|
assert invalid_category.json()["detail"] == "分类不存在"
|
|
assert invalid_extension.status_code == 400
|
|
assert list(files_dir.iterdir()) == []
|
|
|
|
|
|
def test_upload_rejects_oversized_file_without_disk_artifact(files_client):
|
|
client, _, files_dir = files_client
|
|
|
|
response = client.post(
|
|
"/api/files/upload",
|
|
files={"file": ("large.txt", b"x" * (1024 * 1024 + 1), "text/plain")},
|
|
)
|
|
|
|
assert response.status_code == 413
|
|
assert list(files_dir.iterdir()) == []
|
|
assert client.get("/api/files").json() == []
|
|
|
|
|
|
def test_delete_category_removes_descendant_files(files_client):
|
|
client, _, files_dir = files_client
|
|
root = _create_category(client, "根分类")
|
|
child = _create_category(client, "子分类", root["id"])
|
|
uploaded = client.post(
|
|
"/api/files/upload",
|
|
data={"category_id": child["id"]},
|
|
files={"file": ("child.txt", b"content", "text/plain")},
|
|
)
|
|
assert uploaded.status_code == 200
|
|
assert len(list(files_dir.iterdir())) == 1
|
|
|
|
response = client.delete(f"/api/files/categories/{root['id']}")
|
|
|
|
assert response.status_code == 200
|
|
assert client.get("/api/files/categories").json() == []
|
|
assert client.get("/api/files").json() == []
|
|
assert list(files_dir.iterdir()) == []
|