3
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
"""Phase 9-11 公共 AI 页面测试。"""
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
|
||||
|
||||
def _client() -> TestClient:
|
||||
return TestClient(app, raise_server_exceptions=False)
|
||||
|
||||
|
||||
def _register_and_create_kb(client: TestClient) -> tuple[str, str]:
|
||||
"""注册用户并创建知识库,返回 (kb_id, ai_url)。"""
|
||||
client.post("/api/auth/register", json={
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"password": "password123",
|
||||
})
|
||||
resp = client.post("/api/knowledge-bases", json={
|
||||
"name": "测试知识库",
|
||||
"description": "这是测试描述",
|
||||
})
|
||||
kb_id = resp.json()["id"]
|
||||
ai_url = resp.json()["ai_url"]
|
||||
return kb_id, ai_url
|
||||
|
||||
|
||||
def _get_token_from_url(ai_url: str) -> str:
|
||||
"""从 AI URL 中提取 token。"""
|
||||
return ai_url.split("/k/")[-1]
|
||||
|
||||
|
||||
# --- HTML 入口 ---
|
||||
|
||||
def test_kb_index_html() -> None:
|
||||
with _client() as client:
|
||||
_, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
resp = client.get(f"/k/{token}")
|
||||
assert resp.status_code == 200
|
||||
assert "text/html" in resp.headers["content-type"]
|
||||
assert "测试知识库" in resp.text
|
||||
assert "noindex" in resp.text
|
||||
assert "no-referrer" in resp.text
|
||||
|
||||
|
||||
def test_kb_index_html_not_found() -> None:
|
||||
with _client() as client:
|
||||
resp = client.get("/k/nonexistent")
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
def test_kb_index_html_disabled() -> None:
|
||||
with _client() as client:
|
||||
kb_id, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
# 禁用链接
|
||||
client.post(f"/api/knowledge-bases/{kb_id}/disable")
|
||||
resp = client.get(f"/k/{token}")
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
# --- Markdown 输出 ---
|
||||
|
||||
def test_kb_index_markdown() -> None:
|
||||
with _client() as client:
|
||||
_, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
resp = client.get(f"/k/{token}.md")
|
||||
assert resp.status_code == 200
|
||||
assert "text/markdown" in resp.headers["content-type"]
|
||||
assert "测试知识库" in resp.text
|
||||
|
||||
|
||||
# --- 纯文本输出 ---
|
||||
|
||||
def test_kb_index_text() -> None:
|
||||
with _client() as client:
|
||||
_, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
resp = client.get(f"/k/{token}.txt")
|
||||
assert resp.status_code == 200
|
||||
assert "text/plain" in resp.headers["content-type"]
|
||||
assert "测试知识库" in resp.text
|
||||
|
||||
|
||||
# --- JSON 输出 ---
|
||||
|
||||
def test_kb_index_json() -> None:
|
||||
with _client() as client:
|
||||
_, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
resp = client.get(f"/k/{token}.json")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["name"] == "测试知识库"
|
||||
assert "documents" in data
|
||||
# 安全:不包含内部字段
|
||||
assert "user_id" not in data
|
||||
assert "token_hash" not in data
|
||||
|
||||
|
||||
# --- 搜索 ---
|
||||
|
||||
def test_search_html() -> None:
|
||||
with _client() as client:
|
||||
_, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
resp = client.get(f"/k/{token}/search?q=test")
|
||||
assert resp.status_code == 200
|
||||
assert "text/html" in resp.headers["content-type"]
|
||||
|
||||
|
||||
def test_search_json() -> None:
|
||||
with _client() as client:
|
||||
_, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
resp = client.get(f"/k/{token}/search.json?q=test")
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "query" in data
|
||||
assert "total" in data
|
||||
|
||||
|
||||
# --- 单文档访问 ---
|
||||
|
||||
def test_doc_page_html() -> None:
|
||||
"""上传文档后通过 token 访问文档页面。"""
|
||||
import io, zipfile
|
||||
|
||||
with _client() as client:
|
||||
kb_id, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
|
||||
# 上传一个真实 .docx 文件
|
||||
buf = io.BytesIO()
|
||||
with zipfile.ZipFile(buf, "w") as zf:
|
||||
zf.writestr("[Content_Types].xml", '<?xml version="1.0"?><Types></Types>')
|
||||
zf.writestr("word/document.xml", '<?xml version="1.0"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>Hello World</w:t></w:r></w:p></w:body></w:document>')
|
||||
|
||||
# 获取文档 token
|
||||
resp = client.get(f"/api/documents?kb_id={kb_id}")
|
||||
if resp.json()["total"] > 0:
|
||||
doc = resp.json()["items"][0]
|
||||
doc_token_hint = doc.get("doc_token_hint", "")
|
||||
# 访问文档页面(使用 hint 作为 token 的一部分)
|
||||
resp = client.get(f"/k/{token}/doc/{doc_token_hint}")
|
||||
# 可能 404(因为 hint 不是完整 token),但不应 500
|
||||
assert resp.status_code in (200, 404)
|
||||
|
||||
|
||||
# --- 限流 ---
|
||||
|
||||
def test_rate_limit_not_triggered() -> None:
|
||||
"""正常请求不应触发限流。"""
|
||||
with _client() as client:
|
||||
_, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
# 连续请求 5 次
|
||||
for _ in range(5):
|
||||
resp = client.get(f"/k/{token}")
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
# --- 安全头 ---
|
||||
|
||||
def test_no_robots_meta() -> None:
|
||||
with _client() as client:
|
||||
_, ai_url = _register_and_create_kb(client)
|
||||
token = _get_token_from_url(ai_url)
|
||||
resp = client.get(f"/k/{token}")
|
||||
assert "noindex" in resp.text
|
||||
assert "nofollow" in resp.text
|
||||
assert "noarchive" in resp.text
|
||||
Reference in New Issue
Block a user