MaiBot/pytests/webui/test_jargon_routes.py

513 lines
16 KiB
Python

"""测试 jargon 路由的完整性和正确性"""
import json
from datetime import datetime
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy.pool import StaticPool
from sqlmodel import Session, SQLModel, create_engine
from src.common.database.database_model import ChatSession, Jargon
from src.webui.routers.jargon import router as jargon_router
@pytest.fixture(name="app", scope="function")
def app_fixture():
app = FastAPI()
app.include_router(jargon_router, prefix="/api/webui")
return app
@pytest.fixture(name="engine", scope="function")
def engine_fixture():
engine = create_engine(
"sqlite://",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.create_all(engine)
yield engine
@pytest.fixture(name="session", scope="function")
def session_fixture(engine):
connection = engine.connect()
transaction = connection.begin()
session = Session(bind=connection)
yield session
session.close()
transaction.rollback()
connection.close()
@pytest.fixture(name="client", scope="function")
def client_fixture(app: FastAPI, session: Session, monkeypatch):
from contextlib import contextmanager
@contextmanager
def mock_get_db_session():
yield session
monkeypatch.setattr("src.webui.routers.jargon.get_db_session", mock_get_db_session)
with TestClient(app) as client:
yield client
@pytest.fixture(name="sample_chat_session")
def sample_chat_session_fixture(session: Session):
"""创建示例 ChatSession"""
chat_session = ChatSession(
session_id="test_stream_001",
platform="qq",
group_id="123456789",
user_id=None,
created_timestamp=datetime.now(),
last_active_timestamp=datetime.now(),
)
session.add(chat_session)
session.commit()
session.refresh(chat_session)
return chat_session
@pytest.fixture(name="sample_jargons")
def sample_jargons_fixture(session: Session, sample_chat_session: ChatSession):
"""创建示例 Jargon 数据"""
jargons = [
Jargon(
id=1,
content="yyds",
raw_content="永远的神",
meaning="永远的神",
session_id=sample_chat_session.session_id,
count=10,
is_jargon=True,
is_complete=False,
),
Jargon(
id=2,
content="awsl",
raw_content="啊我死了",
meaning="啊我死了",
session_id=sample_chat_session.session_id,
count=5,
is_jargon=True,
is_complete=False,
),
Jargon(
id=3,
content="hello",
raw_content=None,
meaning="你好",
session_id=sample_chat_session.session_id,
count=2,
is_jargon=False,
is_complete=False,
),
]
for jargon in jargons:
session.add(jargon)
session.commit()
for jargon in jargons:
session.refresh(jargon)
return jargons
# ==================== Test Cases ====================
def test_list_jargons(client: TestClient, sample_jargons):
"""测试 GET /jargon/list 基础列表功能"""
response = client.get("/api/webui/jargon/list")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["total"] == 3
assert data["page"] == 1
assert data["page_size"] == 20
assert len(data["data"]) == 3
assert data["data"][0]["content"] == "yyds"
assert data["data"][0]["count"] == 10
def test_list_jargons_with_pagination(client: TestClient, sample_jargons):
"""测试分页功能"""
response = client.get("/api/webui/jargon/list?page=1&page_size=2")
assert response.status_code == 200
data = response.json()
assert data["total"] == 3
assert len(data["data"]) == 2
response = client.get("/api/webui/jargon/list?page=2&page_size=2")
assert response.status_code == 200
data = response.json()
assert len(data["data"]) == 1
def test_list_jargons_with_search(client: TestClient, sample_jargons):
"""测试 GET /jargon/list?search=xxx 搜索功能"""
response = client.get("/api/webui/jargon/list?search=yyds")
assert response.status_code == 200
data = response.json()
assert data["total"] == 1
assert data["data"][0]["content"] == "yyds"
# 测试搜索 meaning
response = client.get("/api/webui/jargon/list?search=你好")
assert response.status_code == 200
data = response.json()
assert data["total"] == 1
assert data["data"][0]["content"] == "hello"
def test_list_jargons_with_chat_id_filter(client: TestClient, sample_jargons, sample_chat_session: ChatSession):
"""测试按 chat_id 筛选"""
response = client.get(f"/api/webui/jargon/list?chat_id={sample_chat_session.session_id}")
assert response.status_code == 200
data = response.json()
assert data["total"] == 3
# 测试不存在的 chat_id
response = client.get("/api/webui/jargon/list?chat_id=nonexistent")
assert response.status_code == 200
data = response.json()
assert data["total"] == 0
def test_list_jargons_with_is_jargon_filter(client: TestClient, sample_jargons):
"""测试按 is_jargon 筛选"""
response = client.get("/api/webui/jargon/list?is_jargon=true")
assert response.status_code == 200
data = response.json()
assert data["total"] == 2
assert all(item["is_jargon"] is True for item in data["data"])
response = client.get("/api/webui/jargon/list?is_jargon=false")
assert response.status_code == 200
data = response.json()
assert data["total"] == 1
assert data["data"][0]["content"] == "hello"
def test_get_jargon_detail(client: TestClient, sample_jargons):
"""测试 GET /jargon/{id} 获取详情"""
jargon_id = sample_jargons[0].id
response = client.get(f"/api/webui/jargon/{jargon_id}")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["id"] == jargon_id
assert data["data"]["content"] == "yyds"
assert data["data"]["meaning"] == "永远的神"
assert data["data"]["count"] == 10
assert data["data"]["is_jargon"] is True
def test_get_jargon_detail_not_found(client: TestClient):
"""测试获取不存在的黑话详情"""
response = client.get("/api/webui/jargon/99999")
assert response.status_code == 404
assert "黑话不存在" in response.json()["detail"]
@pytest.mark.skip(reason="Composite PK (id+content) prevents autoincrement - database model issue")
def test_create_jargon(client: TestClient, sample_chat_session: ChatSession):
"""测试 POST /jargon/ 创建黑话"""
request_data = {
"content": "新黑话",
"raw_content": "原始内容",
"meaning": "含义",
"chat_id": sample_chat_session.session_id,
}
response = client.post("/api/webui/jargon/", json=request_data)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "创建成功"
assert data["data"]["content"] == "新黑话"
assert data["data"]["meaning"] == "含义"
assert data["data"]["count"] == 0
assert data["data"]["is_jargon"] is None
assert data["data"]["is_complete"] is False
def test_create_duplicate_jargon(client: TestClient, sample_jargons, sample_chat_session: ChatSession):
"""测试创建重复黑话返回 400"""
request_data = {
"content": "yyds",
"meaning": "重复的",
"chat_id": sample_chat_session.session_id,
}
response = client.post("/api/webui/jargon/", json=request_data)
assert response.status_code == 400
assert "已存在相同内容的黑话" in response.json()["detail"]
def test_update_jargon(client: TestClient, sample_jargons):
"""测试 PATCH /jargon/{id} 更新黑话"""
jargon_id = sample_jargons[0].id
update_data = {
"meaning": "更新后的含义",
"is_jargon": True,
}
response = client.patch(f"/api/webui/jargon/{jargon_id}", json=update_data)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "更新成功"
assert data["data"]["meaning"] == "更新后的含义"
assert data["data"]["is_jargon"] is True
assert data["data"]["content"] == "yyds" # 未改变的字段保持不变
def test_update_jargon_with_chat_id_mapping(client: TestClient, sample_jargons):
"""测试更新时 chat_id → session_id 的映射"""
jargon_id = sample_jargons[0].id
update_data = {
"chat_id": "new_session_id",
}
response = client.patch(f"/api/webui/jargon/{jargon_id}", json=update_data)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["chat_id"] == "new_session_id"
def test_update_jargon_not_found(client: TestClient):
"""测试更新不存在的黑话"""
response = client.patch("/api/webui/jargon/99999", json={"meaning": "test"})
assert response.status_code == 404
assert "黑话不存在" in response.json()["detail"]
def test_delete_jargon(client: TestClient, sample_jargons, session: Session):
"""测试 DELETE /jargon/{id} 删除黑话"""
jargon_id = sample_jargons[0].id
response = client.delete(f"/api/webui/jargon/{jargon_id}")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "删除成功"
assert data["deleted_count"] == 1
# 验证数据库中已删除
response = client.get(f"/api/webui/jargon/{jargon_id}")
assert response.status_code == 404
def test_delete_jargon_not_found(client: TestClient):
"""测试删除不存在的黑话"""
response = client.delete("/api/webui/jargon/99999")
assert response.status_code == 404
assert "黑话不存在" in response.json()["detail"]
def test_batch_delete(client: TestClient, sample_jargons):
"""测试 POST /jargon/batch/delete 批量删除"""
ids_to_delete = [sample_jargons[0].id, sample_jargons[1].id]
request_data = {"ids": ids_to_delete}
response = client.post("/api/webui/jargon/batch/delete", json=request_data)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["deleted_count"] == 2
assert "成功删除 2 条黑话" in data["message"]
# 验证已删除
response = client.get(f"/api/webui/jargon/{ids_to_delete[0]}")
assert response.status_code == 404
def test_batch_delete_empty_list(client: TestClient):
"""测试批量删除空列表返回 400"""
response = client.post("/api/webui/jargon/batch/delete", json={"ids": []})
assert response.status_code == 400
assert "ID列表不能为空" in response.json()["detail"]
def test_batch_set_jargon_status(client: TestClient, sample_jargons):
"""测试批量设置黑话状态"""
ids = [sample_jargons[0].id, sample_jargons[1].id]
response = client.post(
"/api/webui/jargon/batch/set-jargon",
params={"ids": ids, "is_jargon": False},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert "成功更新 2 条黑话状态" in data["message"]
# 验证状态已更新
detail_response = client.get(f"/api/webui/jargon/{ids[0]}")
assert detail_response.json()["data"]["is_jargon"] is False
def test_get_stats(client: TestClient, sample_jargons):
"""测试 GET /jargon/stats/summary 统计数据"""
response = client.get("/api/webui/jargon/stats/summary")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
stats = data["data"]
assert stats["total"] == 3
assert stats["confirmed_jargon"] == 2
assert stats["confirmed_not_jargon"] == 1
assert stats["pending"] == 0
assert stats["complete_count"] == 0
assert stats["chat_count"] == 1
assert isinstance(stats["top_chats"], dict)
def test_get_chat_list(client: TestClient, sample_jargons, sample_chat_session: ChatSession):
"""测试 GET /jargon/chats 获取聊天列表"""
response = client.get("/api/webui/jargon/chats")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert len(data["data"]) == 1
chat_info = data["data"][0]
assert chat_info["chat_id"] == sample_chat_session.session_id
assert chat_info["platform"] == "qq"
assert chat_info["is_group"] is True
assert chat_info["chat_name"] == sample_chat_session.group_id
def test_get_chat_list_with_json_chat_id(client: TestClient, session: Session, sample_chat_session: ChatSession):
"""测试解析 JSON 格式的 chat_id"""
json_chat_id = json.dumps([[sample_chat_session.session_id, "user123"]])
jargon = Jargon(
id=100,
content="测试黑话",
meaning="测试",
session_id=json_chat_id,
count=1,
)
session.add(jargon)
session.commit()
response = client.get("/api/webui/jargon/chats")
assert response.status_code == 200
data = response.json()
assert len(data["data"]) == 1
assert data["data"][0]["chat_id"] == sample_chat_session.session_id
def test_get_chat_list_without_chat_session(client: TestClient, session: Session):
"""测试聊天列表中没有对应 ChatSession 的情况"""
jargon = Jargon(
id=101,
content="孤立黑话",
meaning="无对应会话",
session_id="nonexistent_stream_id",
count=1,
)
session.add(jargon)
session.commit()
response = client.get("/api/webui/jargon/chats")
assert response.status_code == 200
data = response.json()
assert len(data["data"]) == 1
assert data["data"][0]["chat_id"] == "nonexistent_stream_id"
assert data["data"][0]["chat_name"] == "nonexistent_stream_id"[:20]
assert data["data"][0]["platform"] is None
assert data["data"][0]["is_group"] is False
def test_jargon_response_fields(client: TestClient, sample_jargons, sample_chat_session: ChatSession):
"""测试 JargonResponse 字段完整性"""
response = client.get(f"/api/webui/jargon/{sample_jargons[0].id}")
assert response.status_code == 200
data = response.json()["data"]
# 验证所有必需字段存在
required_fields = [
"id",
"content",
"raw_content",
"meaning",
"chat_id",
"stream_id",
"chat_name",
"count",
"is_jargon",
"is_complete",
"inference_with_context",
"inference_content_only",
]
for field in required_fields:
assert field in data
# 验证 chat_name 显示逻辑
assert data["chat_name"] == sample_chat_session.group_id
@pytest.mark.skip(reason="Composite PK (id+content) prevents autoincrement - database model issue")
def test_create_jargon_without_optional_fields(client: TestClient, sample_chat_session: ChatSession):
"""测试创建黑话时可选字段为空"""
request_data = {
"content": "简单黑话",
"chat_id": sample_chat_session.session_id,
}
response = client.post("/api/webui/jargon/", json=request_data)
assert response.status_code == 200
data = response.json()["data"]
assert data["raw_content"] is None
assert data["meaning"] == ""
def test_update_jargon_partial_fields(client: TestClient, sample_jargons):
"""测试增量更新(只更新部分字段)"""
jargon_id = sample_jargons[0].id
original_content = sample_jargons[0].content
# 只更新 meaning
response = client.patch(f"/api/webui/jargon/{jargon_id}", json={"meaning": "新含义"})
assert response.status_code == 200
data = response.json()["data"]
assert data["meaning"] == "新含义"
assert data["content"] == original_content # 其他字段不变
def test_list_jargons_multiple_filters(client: TestClient, sample_jargons, sample_chat_session: ChatSession):
"""测试组合多个过滤条件"""
response = client.get(f"/api/webui/jargon/list?search=永远&chat_id={sample_chat_session.session_id}&is_jargon=true")
assert response.status_code == 200
data = response.json()
assert data["total"] == 1
assert data["data"][0]["content"] == "yyds"