"""测试 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"