merge: 同步上游 dev 最新内容
This commit is contained in:
@@ -193,7 +193,6 @@ def _build_incoming_message(
|
||||
message.is_command = False
|
||||
message.is_notify = False
|
||||
message.processed_plain_text = text
|
||||
message.display_message = text
|
||||
message.initialized = True
|
||||
return message
|
||||
|
||||
|
||||
@@ -754,15 +754,6 @@ def test_default_bootstrapper_can_migrate_legacy_v1_database(tmp_path: Path) ->
|
||||
"""
|
||||
)
|
||||
).mappings().one()
|
||||
action_row = connection.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT session_id, action_name, action_display_prompt
|
||||
FROM action_records
|
||||
WHERE action_id = 'action-1'
|
||||
"""
|
||||
)
|
||||
).mappings().one()
|
||||
tool_row = connection.execute(
|
||||
text(
|
||||
"""
|
||||
@@ -796,6 +787,8 @@ def test_default_bootstrapper_can_migrate_legacy_v1_database(tmp_path: Path) ->
|
||||
assert snapshot.has_table("chat_sessions")
|
||||
assert snapshot.has_table("mai_messages")
|
||||
assert snapshot.has_table("tool_records")
|
||||
assert not snapshot.has_table("action_records")
|
||||
assert not snapshot.has_column("mai_messages", "display_message")
|
||||
|
||||
unpacked_raw_content = msgpack.unpackb(message_row["raw_content"], raw=False)
|
||||
additional_config = json.loads(message_row["additional_config"])
|
||||
@@ -807,9 +800,6 @@ def test_default_bootstrapper_can_migrate_legacy_v1_database(tmp_path: Path) ->
|
||||
assert message_row["processed_plain_text"] == "你好"
|
||||
assert unpacked_raw_content == [{"type": "text", "data": "你好呀"}]
|
||||
assert additional_config == {"priority_mode": "high", "source": "legacy"}
|
||||
assert action_row["session_id"] == "session-1"
|
||||
assert action_row["action_name"] == "search"
|
||||
assert action_row["action_display_prompt"] == "执行搜索"
|
||||
assert tool_row["session_id"] == "session-1"
|
||||
assert tool_row["tool_name"] == "search"
|
||||
assert tool_row["tool_display_prompt"] == "执行搜索"
|
||||
@@ -848,8 +838,8 @@ def test_legacy_v1_migration_reports_table_progress(tmp_path: Path) -> None:
|
||||
|
||||
migration_plan = manager.migrate(target_version=LATEST_SCHEMA_VERSION)
|
||||
|
||||
assert migration_plan.step_count() == 1
|
||||
assert len(reporter_instances) == 1
|
||||
assert migration_plan.step_count() == 3
|
||||
assert len(reporter_instances) == 3
|
||||
reporter_events = reporter_instances[0].events
|
||||
|
||||
assert reporter_events[0] == ("open", None, None, None)
|
||||
@@ -894,10 +884,6 @@ def test_initialize_database_calls_bootstrapper_before_create_all(
|
||||
del bind
|
||||
call_order.append("create_all")
|
||||
|
||||
def _fake_migrate_action_records() -> None:
|
||||
"""记录轻量补迁移调用。"""
|
||||
call_order.append("migrate_action_records")
|
||||
|
||||
def _fake_finalize_database(migration_state: DatabaseMigrationState) -> None:
|
||||
"""记录迁移收尾调用。
|
||||
|
||||
@@ -912,13 +898,11 @@ def test_initialize_database_calls_bootstrapper_before_create_all(
|
||||
monkeypatch.setattr(database_module._migration_bootstrapper, "prepare_database", _fake_prepare_database)
|
||||
monkeypatch.setattr(database_module._migration_bootstrapper, "finalize_database", _fake_finalize_database)
|
||||
monkeypatch.setattr(database_module.SQLModel.metadata, "create_all", _fake_create_all)
|
||||
monkeypatch.setattr(database_module, "_migrate_action_records_to_tool_records", _fake_migrate_action_records)
|
||||
|
||||
database_module.initialize_database()
|
||||
|
||||
assert call_order == [
|
||||
"prepare_database",
|
||||
"create_all",
|
||||
"migrate_action_records",
|
||||
"finalize_database",
|
||||
]
|
||||
|
||||
11
pytests/config_test/test_model_info_normalization.py
Normal file
11
pytests/config_test/test_model_info_normalization.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from src.config.model_configs import ModelInfo
|
||||
|
||||
|
||||
def test_model_identifier_strips_surrounding_whitespace() -> None:
|
||||
model_info = ModelInfo(
|
||||
api_provider="test-provider",
|
||||
model_identifier=" glm-5.1 ",
|
||||
name="test-model",
|
||||
)
|
||||
|
||||
assert model_info.model_identifier == "glm-5.1"
|
||||
@@ -178,7 +178,6 @@ def _install_stub_modules(monkeypatch):
|
||||
class _EmojiConfig:
|
||||
max_reg_num = 20
|
||||
content_filtration = False
|
||||
filtration_prompt = ""
|
||||
steal_emoji = False
|
||||
do_replace = False
|
||||
check_interval = 1
|
||||
@@ -1956,7 +1955,6 @@ async def test_build_emoji_description_content_filtration_reject(monkeypatch):
|
||||
logger = emoji_manager_new.logger
|
||||
|
||||
emoji_manager_new.global_config.emoji.content_filtration = True
|
||||
emoji_manager_new.global_config.emoji.filtration_prompt = "rule"
|
||||
|
||||
def _read_bytes(_path):
|
||||
return b""
|
||||
@@ -1994,13 +1992,15 @@ async def test_build_emoji_description_content_filtration_pass(monkeypatch):
|
||||
logger = emoji_manager_new.logger
|
||||
|
||||
emoji_manager_new.global_config.emoji.content_filtration = True
|
||||
emoji_manager_new.global_config.emoji.filtration_prompt = "rule"
|
||||
|
||||
def _read_bytes(_path):
|
||||
return b""
|
||||
|
||||
async def _vlm_response(prompt, *_args, **_kwargs):
|
||||
if "rule" in str(prompt):
|
||||
call_count = {"n": 0}
|
||||
|
||||
async def _vlm_response(*_args, **_kwargs):
|
||||
call_count["n"] += 1
|
||||
if call_count["n"] == 2:
|
||||
return "是", None
|
||||
return "desc", None
|
||||
|
||||
|
||||
@@ -87,7 +87,46 @@ def test_list_prompt_templates_prefers_locale_specific_files(tmp_path: Path) ->
|
||||
|
||||
prompt_templates = list_prompt_templates(prompts_root=prompts_root)
|
||||
|
||||
assert prompt_templates["replyer"].read_text(encoding="utf-8") == "English"
|
||||
assert prompt_templates["replyer"].path.read_text(encoding="utf-8") == "English"
|
||||
|
||||
|
||||
def test_list_prompt_templates_loads_directory_metadata(tmp_path: Path) -> None:
|
||||
prompts_root = tmp_path / "prompts"
|
||||
write_prompt(prompts_root, "zh-CN", "replyer", "中文")
|
||||
metadata_path = prompts_root / "zh-CN" / ".meta.toml"
|
||||
metadata_path.write_text(
|
||||
"""
|
||||
[replyer]
|
||||
display_name = "回复器"
|
||||
advanced = true
|
||||
description = "用于生成回复的主模板"
|
||||
""".strip(),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt_templates = list_prompt_templates(prompts_root=prompts_root)
|
||||
metadata = prompt_templates["replyer"].metadata
|
||||
|
||||
assert metadata.display_name == "回复器"
|
||||
assert metadata.advanced is True
|
||||
assert metadata.description == "用于生成回复的主模板"
|
||||
|
||||
|
||||
def test_list_prompt_templates_loads_prompt_specific_metadata(tmp_path: Path) -> None:
|
||||
prompts_root = tmp_path / "prompts"
|
||||
write_prompt(prompts_root, "zh-CN", "replyer", "中文")
|
||||
metadata_path = prompts_root / "zh-CN" / "replyer.meta.json"
|
||||
metadata_path.write_text(
|
||||
'{"display_name": "Replyer", "advanced": false, "description": "Prompt specific metadata"}',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt_templates = list_prompt_templates(prompts_root=prompts_root)
|
||||
metadata = prompt_templates["replyer"].metadata
|
||||
|
||||
assert metadata.display_name == "Replyer"
|
||||
assert metadata.advanced is False
|
||||
assert metadata.description == "Prompt specific metadata"
|
||||
|
||||
|
||||
def test_list_prompt_templates_reports_duplicate_name_with_custom_root(tmp_path: Path) -> None:
|
||||
|
||||
@@ -41,7 +41,6 @@ def test_build_message_returns_session_message_with_maisaka_metadata() -> None:
|
||||
assert message.message_id == "maisaka-msg-1"
|
||||
assert message.timestamp == timestamp
|
||||
assert message.processed_plain_text == "展示消息内容"
|
||||
assert message.display_message == "展示消息内容"
|
||||
assert message.raw_message is raw_message
|
||||
|
||||
assert get_message_role(message) == "assistant"
|
||||
|
||||
@@ -554,7 +554,6 @@ async def test_inbound_codec_resolves_at_to_group_cardname() -> None:
|
||||
)
|
||||
|
||||
assert message_dict["processed_plain_text"] == "@群昵称"
|
||||
assert message_dict["display_message"] == "@群昵称"
|
||||
assert message_dict["raw_message"] == [
|
||||
{
|
||||
"type": "at",
|
||||
@@ -599,7 +598,6 @@ async def test_inbound_codec_falls_back_to_qq_nickname_when_group_cardname_is_em
|
||||
)
|
||||
|
||||
assert message_dict["processed_plain_text"] == "@QQ昵称"
|
||||
assert message_dict["display_message"] == "@QQ昵称"
|
||||
assert message_dict["raw_message"] == [
|
||||
{
|
||||
"type": "at",
|
||||
@@ -640,7 +638,6 @@ async def test_inbound_codec_falls_back_to_stranger_nickname_when_group_profile_
|
||||
)
|
||||
|
||||
assert message_dict["processed_plain_text"] == "@QQ昵称"
|
||||
assert message_dict["display_message"] == "@QQ昵称"
|
||||
assert message_dict["raw_message"] == [
|
||||
{
|
||||
"type": "at",
|
||||
|
||||
@@ -1,16 +1,59 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from src.config.model_configs import APIProvider, ReasoningParseMode, ToolArgumentParseMode
|
||||
from src.llm_models.model_client.openai_client import (
|
||||
_OpenAIStreamAccumulator,
|
||||
_build_reasoning_key,
|
||||
_default_normal_response_parser,
|
||||
_parse_tool_arguments,
|
||||
_sanitize_messages_for_toolless_request,
|
||||
)
|
||||
from src.llm_models.payload_content.message import Message, RoleType, TextMessagePart
|
||||
from src.llm_models.payload_content.tool_option import ToolCall
|
||||
|
||||
|
||||
@pytest.mark.parametrize("parse_mode", list(ToolArgumentParseMode))
|
||||
def test_parse_tool_arguments_treats_blank_arguments_as_empty_dict(parse_mode: ToolArgumentParseMode) -> None:
|
||||
assert _parse_tool_arguments("", parse_mode, None) == {}
|
||||
assert _parse_tool_arguments(" ", parse_mode, None) == {}
|
||||
|
||||
|
||||
def test_normal_response_parser_accepts_empty_string_arguments_for_parameterless_tool() -> None:
|
||||
response = SimpleNamespace(
|
||||
choices=[
|
||||
SimpleNamespace(
|
||||
finish_reason="tool_calls",
|
||||
message=SimpleNamespace(
|
||||
content=None,
|
||||
tool_calls=[
|
||||
SimpleNamespace(
|
||||
id="finish-call",
|
||||
type="function",
|
||||
function=SimpleNamespace(name="finish", arguments=""),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
usage=None,
|
||||
model="glm-5.1",
|
||||
)
|
||||
|
||||
api_response, usage_record = _default_normal_response_parser(
|
||||
response,
|
||||
reasoning_parse_mode=ReasoningParseMode.AUTO,
|
||||
tool_argument_parse_mode=ToolArgumentParseMode.AUTO,
|
||||
reasoning_key=None,
|
||||
)
|
||||
|
||||
assert len(api_response.tool_calls) == 1
|
||||
assert api_response.tool_calls[0].func_name == "finish"
|
||||
assert api_response.tool_calls[0].args == {}
|
||||
assert usage_record is None
|
||||
|
||||
|
||||
def test_sanitize_messages_for_toolless_request_drops_assistant_tool_call_without_parts() -> None:
|
||||
messages = [
|
||||
Message(
|
||||
|
||||
@@ -31,7 +31,6 @@ def test_plugin_message_utils_preserves_binary_components_and_reply_metadata() -
|
||||
)
|
||||
message.session_id = "qq:20001:10001"
|
||||
message.processed_plain_text = "binary payload"
|
||||
message.display_message = "binary payload"
|
||||
message.raw_message = MessageSequence(
|
||||
components=[
|
||||
TextComponent("hello"),
|
||||
|
||||
@@ -298,7 +298,7 @@ async def test_private_outbound_message_preserves_bot_sender_and_receiver_user(
|
||||
outbound_message = send_service._build_outbound_session_message(
|
||||
message_sequence=MessageSequence(components=[TextComponent(text="你好")]),
|
||||
stream_id="test-session",
|
||||
display_message="你好",
|
||||
processed_plain_text="你好",
|
||||
)
|
||||
|
||||
assert outbound_message is not None
|
||||
@@ -329,7 +329,7 @@ async def test_group_outbound_message_preserves_bot_sender_and_target_group(
|
||||
outbound_message = send_service._build_outbound_session_message(
|
||||
message_sequence=MessageSequence(components=[TextComponent(text="大家好")]),
|
||||
stream_id="group-session",
|
||||
display_message="大家好",
|
||||
processed_plain_text="大家好",
|
||||
)
|
||||
|
||||
assert outbound_message is not None
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
"""Expression routes pytest tests"""
|
||||
|
||||
from typing import Generator
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI, APIRouter
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.pool import StaticPool
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.pool import StaticPool
|
||||
from sqlmodel import Session, SQLModel, create_engine, select
|
||||
|
||||
from src.common.database.database_model import Expression
|
||||
from src.common.database.database_model import Expression, ModifiedBy
|
||||
from src.webui.dependencies import require_auth
|
||||
|
||||
|
||||
def create_test_app() -> FastAPI:
|
||||
@@ -63,6 +63,7 @@ def client_fixture(test_session: Session, monkeypatch) -> Generator[TestClient,
|
||||
@contextmanager
|
||||
def get_test_db_session():
|
||||
yield test_session
|
||||
test_session.commit()
|
||||
|
||||
monkeypatch.setattr("src.webui.routers.expression.get_db_session", get_test_db_session)
|
||||
|
||||
@@ -71,10 +72,11 @@ def client_fixture(test_session: Session, monkeypatch) -> Generator[TestClient,
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_auth")
|
||||
def mock_auth_fixture(monkeypatch):
|
||||
def mock_auth_fixture():
|
||||
"""Mock authentication to always return True"""
|
||||
mock_verify = MagicMock(return_value=True)
|
||||
monkeypatch.setattr("src.webui.routers.expression.verify_auth_token_from_cookie_or_header", mock_verify)
|
||||
app.dependency_overrides[require_auth] = lambda: "test-token"
|
||||
yield
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
@pytest.fixture(name="sample_expression")
|
||||
@@ -82,8 +84,8 @@ def sample_expression_fixture(test_session: Session) -> Expression:
|
||||
"""Insert a sample expression into test database"""
|
||||
test_session.execute(
|
||||
text(
|
||||
"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) "
|
||||
"VALUES (1, '测试情景', '测试风格', '测试上下文', '测试上文', '[\"测试内容1\", \"测试内容2\"]', 10, '2026-02-17 12:00:00', '2026-02-15 10:00:00', 'test_chat_001')"
|
||||
"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) "
|
||||
"VALUES (1, '测试情景', '测试风格', '[\"测试内容1\", \"测试内容2\"]', 10, '2026-02-17 12:00:00', '2026-02-15 10:00:00', 'test_chat_001', 0, 0)"
|
||||
)
|
||||
)
|
||||
test_session.commit()
|
||||
@@ -131,8 +133,8 @@ def test_list_expressions_pagination(client: TestClient, mock_auth, test_session
|
||||
for i in range(5):
|
||||
test_session.execute(
|
||||
text(
|
||||
f"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) "
|
||||
f"VALUES ({i + 1}, '情景{i}', '风格{i}', '', '', '[]', 0, '2026-02-17 12:0{i}:00', '2026-02-15 10:00:00', 'chat_{i}')"
|
||||
f"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) "
|
||||
f"VALUES ({i + 1}, '情景{i}', '风格{i}', '[]', 0, '2026-02-17 12:0{i}:00', '2026-02-15 10:00:00', 'chat_{i}', 0, 0)"
|
||||
)
|
||||
)
|
||||
test_session.commit()
|
||||
@@ -158,14 +160,14 @@ def test_list_expressions_search(client: TestClient, mock_auth, test_session: Se
|
||||
"""Test GET /expression/list with search filter"""
|
||||
test_session.execute(
|
||||
text(
|
||||
"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) "
|
||||
"VALUES (1, '找人吃饭', '热情', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_001')"
|
||||
"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) "
|
||||
"VALUES (1, '找人吃饭', '热情', '[]', 0, datetime('now'), datetime('now'), 'chat_001', 0, 0)"
|
||||
)
|
||||
)
|
||||
test_session.execute(
|
||||
text(
|
||||
"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) "
|
||||
"VALUES (2, '拒绝邀请', '礼貌', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_002')"
|
||||
"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) "
|
||||
"VALUES (2, '拒绝邀请', '礼貌', '[]', 0, datetime('now'), datetime('now'), 'chat_002', 0, 0)"
|
||||
)
|
||||
)
|
||||
test_session.commit()
|
||||
@@ -183,14 +185,14 @@ def test_list_expressions_chat_filter(client: TestClient, mock_auth, test_sessio
|
||||
"""Test GET /expression/list with chat_id filter"""
|
||||
test_session.execute(
|
||||
text(
|
||||
"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) "
|
||||
"VALUES (1, '情景A', '风格A', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_A')"
|
||||
"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) "
|
||||
"VALUES (1, '情景A', '风格A', '[]', 0, datetime('now'), datetime('now'), 'chat_A', 0, 0)"
|
||||
)
|
||||
)
|
||||
test_session.execute(
|
||||
text(
|
||||
"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) "
|
||||
"VALUES (2, '情景B', '风格B', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_B')"
|
||||
"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) "
|
||||
"VALUES (2, '情景B', '风格B', '[]', 0, datetime('now'), datetime('now'), 'chat_B', 0, 0)"
|
||||
)
|
||||
)
|
||||
test_session.commit()
|
||||
@@ -378,8 +380,8 @@ def test_batch_delete_expressions_success(client: TestClient, mock_auth, test_se
|
||||
for i in range(3):
|
||||
test_session.execute(
|
||||
text(
|
||||
f"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) "
|
||||
f"VALUES ({i + 1}, '批量删除{i}', '风格{i}', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_{i}')"
|
||||
f"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) "
|
||||
f"VALUES ({i + 1}, '批量删除{i}', '风格{i}', '[]', 0, datetime('now'), datetime('now'), 'chat_{i}', 0, 0)"
|
||||
)
|
||||
)
|
||||
expression_ids.append(i + 1)
|
||||
@@ -416,8 +418,8 @@ def test_get_expression_stats(client: TestClient, mock_auth, test_session: Sessi
|
||||
for i in range(3):
|
||||
test_session.execute(
|
||||
text(
|
||||
f"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) "
|
||||
f"VALUES ({i + 1}, '情景{i}', '风格{i}', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_{i % 2}')"
|
||||
f"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) "
|
||||
f"VALUES ({i + 1}, '情景{i}', '风格{i}', '[]', 0, datetime('now'), datetime('now'), 'chat_{i % 2}', 0, 0)"
|
||||
)
|
||||
)
|
||||
test_session.commit()
|
||||
@@ -432,11 +434,11 @@ def test_get_expression_stats(client: TestClient, mock_auth, test_session: Sessi
|
||||
|
||||
|
||||
def test_get_review_stats(client: TestClient, mock_auth, test_session: Session):
|
||||
"""Test GET /expression/review/stats returns hardcoded 0 counts"""
|
||||
"""Test GET /expression/review/stats returns review status counts"""
|
||||
test_session.execute(
|
||||
text(
|
||||
"INSERT INTO expressions (id, situation, style, context, up_content, content_list, count, last_active_time, create_time, session_id) "
|
||||
"VALUES (1, '待审核', '风格', '', '', '[]', 0, datetime('now'), datetime('now'), 'chat_001')"
|
||||
"INSERT INTO expressions (id, situation, style, content_list, count, last_active_time, create_time, session_id, checked, rejected) "
|
||||
"VALUES (1, '待审核', '风格', '[]', 0, datetime('now'), datetime('now'), 'chat_001', 0, 0)"
|
||||
)
|
||||
)
|
||||
test_session.commit()
|
||||
@@ -445,9 +447,8 @@ def test_get_review_stats(client: TestClient, mock_auth, test_session: Session):
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
# Verify all review counts are 0 (hardcoded in refactored code)
|
||||
assert data["total"] == 1 # Total expressions exists
|
||||
assert data["unchecked"] == 0
|
||||
assert data["unchecked"] == 1
|
||||
assert data["passed"] == 0
|
||||
assert data["rejected"] == 0
|
||||
assert data["ai_checked"] == 0
|
||||
@@ -455,14 +456,14 @@ def test_get_review_stats(client: TestClient, mock_auth, test_session: Session):
|
||||
|
||||
|
||||
def test_get_review_list_filter_unchecked(client: TestClient, mock_auth, sample_expression: Expression):
|
||||
"""Test GET /expression/review/list with filter_type=unchecked returns empty (legacy behavior)"""
|
||||
# filter_type=unchecked should return no results (legacy removed)
|
||||
"""Test GET /expression/review/list with filter_type=unchecked returns unchecked expressions"""
|
||||
response = client.get("/api/webui/expression/review/list?filter_type=unchecked")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert data["success"] is True
|
||||
assert data["total"] == 0 # No results (legacy fields removed)
|
||||
assert data["total"] == 1
|
||||
assert len(data["data"]) == 1
|
||||
|
||||
|
||||
def test_get_review_list_filter_all(client: TestClient, mock_auth, sample_expression: Expression):
|
||||
@@ -476,8 +477,8 @@ def test_get_review_list_filter_all(client: TestClient, mock_auth, sample_expres
|
||||
assert len(data["data"]) == 1
|
||||
|
||||
|
||||
def test_batch_review_expressions_unsupported(client: TestClient, mock_auth, sample_expression: Expression):
|
||||
"""Test POST /expression/review/batch returns failure for require_unchecked=True"""
|
||||
def test_batch_review_expressions_with_unchecked_marker(client: TestClient, mock_auth, sample_expression: Expression):
|
||||
"""Test POST /expression/review/batch succeeds with require_unchecked=True"""
|
||||
review_payload = {"items": [{"id": sample_expression.id, "rejected": False, "require_unchecked": True}]}
|
||||
|
||||
response = client.post("/api/webui/expression/review/batch", json=review_payload)
|
||||
@@ -485,8 +486,34 @@ def test_batch_review_expressions_unsupported(client: TestClient, mock_auth, sam
|
||||
|
||||
data = response.json()
|
||||
assert data["success"] is True
|
||||
assert data["failed"] == 1 # Should fail because require_unchecked=True
|
||||
assert "不支持审核状态过滤" in data["results"][0]["message"]
|
||||
assert data["succeeded"] == 1
|
||||
assert data["results"][0]["success"] is True
|
||||
|
||||
|
||||
def test_batch_review_expressions_overwrites_ai_checked(
|
||||
client: TestClient, mock_auth, test_session: Session, sample_expression: Expression
|
||||
):
|
||||
"""Test POST /expression/review/batch lets manual review override AI checked state"""
|
||||
sample_expression.checked = True
|
||||
sample_expression.rejected = True
|
||||
sample_expression.modified_by = ModifiedBy.AI
|
||||
test_session.add(sample_expression)
|
||||
test_session.commit()
|
||||
|
||||
review_payload = {"items": [{"id": sample_expression.id, "rejected": False, "require_unchecked": True}]}
|
||||
|
||||
response = client.post("/api/webui/expression/review/batch", json=review_payload)
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert data["success"] is True
|
||||
assert data["succeeded"] == 1
|
||||
test_session.expire_all()
|
||||
reviewed_expression = test_session.exec(select(Expression).where(Expression.id == sample_expression.id)).first()
|
||||
assert reviewed_expression is not None
|
||||
assert reviewed_expression.checked is True
|
||||
assert reviewed_expression.rejected is False
|
||||
assert reviewed_expression.modified_by == ModifiedBy.USER
|
||||
|
||||
|
||||
def test_batch_review_expressions_no_unchecked_check(client: TestClient, mock_auth, sample_expression: Expression):
|
||||
|
||||
Reference in New Issue
Block a user