agent-Specialization/test/test_conversation_model_persistence.py
JOJO aa9bf377a3 feat(runtime): Gateway 收尾——session 传输暴露、host Bearer 通道、flask 依赖拆解、审批链路收口
- session.* 传输暴露:新增 gateway_api 蓝图(list/create/history),RuntimeService 补 create_session
- host Bearer 通道:gateway_auth 双通道认证(host 模式+回环限定,token 存 DATA_DIR/host_api_token),tasks/approval 路由接入
- api_v1 会话路由转调公共入口消双轨,会话索引/列表补齐 run_mode/model_key/custom_prompt_name/personalization_name
- flask 包依赖拆解:_flask_bridge 延迟桥接 + context 子包 PEP 562 懒加载,import server.tasks/runtime 不再拉起 flask
- 审批链路:mark_expired 终态回写(超时/软停止/取消三路径)+ 终态 TTL 惰性清理(3600s)
- 测试 patch 点随迁;全量 75 测试失败恰为 4 项存量,独立启动验收 4/4 全绿

Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
2026-09-08 11:37:51 +08:00

167 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""回归测试:对话模型持久化与 /new 页面默认模型行为。"""
import os
import sys
import tempfile
import unittest
from unittest.mock import MagicMock, patch
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from server.context import _apply_workspace_personalization_preferences
# server/context.py 拆分为子包后,目标函数实际位于 personalization 子模块;
# patch 必须指向使用处的模块命名空间才能生效。
_PERSONALIZATION_NS = "server.context.personalization"
class FakeSession:
def __init__(self):
self._data = {}
def get(self, key, default=None):
return self._data.get(key, default)
def __setitem__(self, key, value):
self._data[key] = value
class TestApplyWorkspacePersonalizationPreferences(unittest.TestCase):
def _make_terminal(self, model_key="default-model"):
terminal = MagicMock()
terminal.model_key = model_key
terminal._workspace_default_model_applied = False
# MagicMock 的下划线属性默认是 truthy 子 mock本组用例测「未绑定对话」
# 的工作区级恢复路径,必须显式置 Noneis_conversation_bound 防护)。
terminal._bound_conversation_id = None
def _apply_personalization_preferences(config, apply_default_model=True, **kwargs):
# 生产代码调用时还传 apply_default_modes 等关键字side_effect 必须兼容
if apply_default_model:
default_model = (config or {}).get("default_model")
if default_model:
terminal.model_key = default_model
terminal.apply_personalization_preferences = MagicMock(side_effect=_apply_personalization_preferences)
return terminal
def _make_workspace(self):
workspace = MagicMock()
workspace.data_dir = tempfile.mkdtemp()
return workspace
@patch(f"{_PERSONALIZATION_NS}.load_personalization_config")
@patch(f"{_PERSONALIZATION_NS}.has_request_context", return_value=True)
def test_session_model_restored(self, _hrc, mock_load_config):
"""session 中保存了模型时,应恢复到该模型。"""
mock_load_config.return_value = {"default_model": "default-model"}
terminal = self._make_terminal(model_key="old-model")
terminal.set_model = MagicMock(side_effect=lambda mk: setattr(terminal, "model_key", mk))
workspace = self._make_workspace()
session = FakeSession()
session["model_key"] = "session-model"
# flask bridge 化后2026-09-08session 读写经 session_get/session_set
# patch 点随之指向 bridge 函数而非模块级 session 符号。
with patch(f"{_PERSONALIZATION_NS}.session_get", side_effect=lambda key, default=None: session.get(key, default)), \
patch(f"{_PERSONALIZATION_NS}.session_set", side_effect=lambda key, value: session.__setitem__(key, value)):
_apply_workspace_personalization_preferences(terminal, workspace)
terminal.set_model.assert_called_once_with("session-model")
self.assertEqual(session.get("model_key"), "session-model")
@patch(f"{_PERSONALIZATION_NS}.load_personalization_config")
@patch(f"{_PERSONALIZATION_NS}.has_request_context", return_value=True)
def test_default_model_applied_for_fresh_session(self, _hrc, mock_load_config):
"""没有 session 模型时,应应用默认模型(且仅一次)。"""
mock_load_config.return_value = {"default_model": "default-model"}
terminal = self._make_terminal(model_key="kimi-k2.6")
workspace = self._make_workspace()
session = FakeSession()
with patch(f"{_PERSONALIZATION_NS}.session_get", side_effect=lambda key, default=None: session.get(key, default)), \
patch(f"{_PERSONALIZATION_NS}.session_set", side_effect=lambda key, value: session.__setitem__(key, value)):
_apply_workspace_personalization_preferences(terminal, workspace)
terminal.set_model.assert_not_called()
self.assertEqual(terminal.model_key, "default-model")
self.assertEqual(session.get("model_key"), "default-model")
self.assertTrue(terminal._workspace_default_model_applied)
class TestLoadConversationRestoreModel(unittest.TestCase):
@patch("core.web_terminal.logger")
def test_restore_model_true_restores_saved_model(self, _mock_logger):
"""显式加载对话时应恢复对话保存的模型。"""
from core.web_terminal import WebTerminal
terminal = MagicMock(spec=WebTerminal)
terminal.model_key = "default-model"
terminal.thinking_mode = False
terminal.run_mode = "fast"
terminal.multi_agent_mode = False
terminal.context_manager = MagicMock()
terminal.context_manager.load_conversation_by_id.return_value = True
cm = MagicMock()
cm.load_conversation.return_value = {
"metadata": {
"thinking_mode": False,
"model_key": "saved-model",
"multi_agent_mode": False,
}
}
terminal.context_manager._get_conversation_manager_for_id.return_value = cm
terminal.set_model = MagicMock(side_effect=lambda mk: setattr(terminal, "model_key", mk))
terminal.set_permission_mode = MagicMock()
terminal.set_execution_mode = MagicMock()
terminal.set_network_permission = MagicMock()
terminal.api_client = MagicMock()
terminal.current_session_id = 0
# 调用实际方法
result = WebTerminal.load_conversation(terminal, "conv_test_001", restore_model=True)
terminal.set_model.assert_called_once_with("saved-model")
self.assertEqual(terminal.model_key, "saved-model")
self.assertTrue(result.get("success"))
@patch("core.web_terminal.logger")
def test_restore_model_false_keeps_current_model(self, _mock_logger):
"""程序启动自动恢复最近对话时不应恢复模型,避免 /new 页面显示旧模型。"""
from core.web_terminal import WebTerminal
terminal = MagicMock(spec=WebTerminal)
terminal.model_key = "default-model"
terminal.thinking_mode = False
terminal.run_mode = "fast"
terminal.multi_agent_mode = False
terminal.context_manager = MagicMock()
terminal.context_manager.load_conversation_by_id.return_value = True
cm = MagicMock()
cm.load_conversation.return_value = {
"metadata": {
"thinking_mode": False,
"model_key": "saved-model",
"multi_agent_mode": False,
}
}
terminal.context_manager._get_conversation_manager_for_id.return_value = cm
terminal.set_model = MagicMock(side_effect=lambda mk: setattr(terminal, "model_key", mk))
terminal.set_permission_mode = MagicMock()
terminal.set_execution_mode = MagicMock()
terminal.set_network_permission = MagicMock()
terminal.api_client = MagicMock()
terminal.current_session_id = 0
result = WebTerminal.load_conversation(terminal, "conv_test_001", restore_model=False)
terminal.set_model.assert_not_called()
self.assertEqual(terminal.model_key, "default-model")
self.assertTrue(result.get("success"))
if __name__ == "__main__":
unittest.main()