mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-21 23:44:31 +08:00
222 lines
7.7 KiB
Python
222 lines
7.7 KiB
Python
import asyncio
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
from app import schemas
|
|
from app.agent import ReplyMode
|
|
from app.api.endpoints.agent import (
|
|
_WebAgentMoviePilotAgent,
|
|
_build_web_agent_notification_events,
|
|
_build_web_agent_session_id,
|
|
_resolve_web_agent_choice_payload,
|
|
_split_web_agent_output,
|
|
)
|
|
from app.helper.interaction import AgentInteractionOption, agent_interaction_manager
|
|
from app.schemas.message import ChannelCapability, ChannelCapabilityManager
|
|
from app.schemas.types import MessageChannel, NotificationType
|
|
|
|
|
|
def test_split_web_agent_output_extracts_verbose_tool_message():
|
|
"""应将啰嗦模式工具提示拆成独立工具事件。"""
|
|
events = _split_web_agent_output("准备查询。\n\n⚙️ => 查询站点\n\n已完成")
|
|
|
|
assert events == [
|
|
{"type": "delta", "content": "准备查询。\n\n"},
|
|
{"type": "tool", "message": "查询站点"},
|
|
{"type": "delta", "content": "已完成"},
|
|
]
|
|
|
|
|
|
def test_split_web_agent_output_extracts_summary_tool_message():
|
|
"""应将非啰嗦模式工具汇总行拆成独立工具事件。"""
|
|
events = _split_web_agent_output("(查询了 2 次数据)\n\n这里是结果")
|
|
|
|
assert events == [
|
|
{"type": "tool", "message": "查询了 2 次数据"},
|
|
{"type": "delta", "content": "\n这里是结果"},
|
|
]
|
|
|
|
|
|
def test_build_web_agent_session_id_is_stable_per_user_and_seed():
|
|
"""同一用户和前端会话标识应生成稳定的服务端会话 ID。"""
|
|
user = SimpleNamespace(id=1, name="admin")
|
|
|
|
first = _build_web_agent_session_id(user, "browser-session")
|
|
second = _build_web_agent_session_id(user, "browser-session")
|
|
other = _build_web_agent_session_id(user, "other-session")
|
|
|
|
assert first == second
|
|
assert first != other
|
|
assert first.startswith("web-agent:")
|
|
|
|
|
|
def test_web_agent_admin_context_uses_current_user_id():
|
|
"""Web Agent 工具权限应按当前登录用户 ID 判断管理员身份。"""
|
|
agent = _WebAgentMoviePilotAgent(
|
|
session_id="web-agent:session",
|
|
user_id="7",
|
|
channel=MessageChannel.WebAgent.value,
|
|
source="web-agent",
|
|
username="normal-user",
|
|
replay_mode=ReplyMode.CAPTURE_ONLY,
|
|
)
|
|
|
|
with patch("app.api.endpoints.agent.UserOper") as user_oper:
|
|
user_oper.return_value.async_get_by_id = AsyncMock(
|
|
return_value=SimpleNamespace(is_superuser=True)
|
|
)
|
|
|
|
assert asyncio.run(agent._is_system_admin_context()) is True
|
|
user_oper.return_value.async_get_by_id.assert_awaited_once_with(7)
|
|
|
|
|
|
def test_web_agent_channel_supports_streaming_and_attachments():
|
|
"""WebAgent 渠道应声明流式、多媒体和文件发送能力。"""
|
|
assert ChannelCapabilityManager.supports_capability(
|
|
MessageChannel.WebAgent, ChannelCapability.INLINE_BUTTONS
|
|
)
|
|
assert ChannelCapabilityManager.supports_capability(
|
|
MessageChannel.WebAgent, ChannelCapability.CALLBACK_QUERIES
|
|
)
|
|
assert ChannelCapabilityManager.supports_capability(
|
|
MessageChannel.WebAgent, ChannelCapability.MESSAGE_EDITING
|
|
)
|
|
assert ChannelCapabilityManager.supports_capability(
|
|
MessageChannel.WebAgent, ChannelCapability.IMAGES
|
|
)
|
|
assert ChannelCapabilityManager.supports_capability(
|
|
MessageChannel.WebAgent, ChannelCapability.AUDIO_OUTPUT
|
|
)
|
|
assert ChannelCapabilityManager.supports_capability(
|
|
MessageChannel.WebAgent, ChannelCapability.FILE_SENDING
|
|
)
|
|
|
|
|
|
def test_build_web_agent_notification_events_extracts_image():
|
|
"""Agent 工具发送图片消息时应转换为图片附件事件。"""
|
|
events = _build_web_agent_notification_events(
|
|
schemas.Notification(
|
|
channel=MessageChannel.WebAgent,
|
|
mtype=NotificationType.Agent,
|
|
title="海报",
|
|
text="已找到图片",
|
|
image="https://example.com/poster.jpg",
|
|
)
|
|
)
|
|
|
|
assert events == [
|
|
{"type": "delta", "content": "海报\n\n已找到图片"},
|
|
{
|
|
"type": "attachment",
|
|
"attachment": {
|
|
"kind": "image",
|
|
"url": "https://example.com/poster.jpg",
|
|
"download_url": "https://example.com/poster.jpg",
|
|
"name": "海报",
|
|
"mime_type": None,
|
|
},
|
|
},
|
|
]
|
|
|
|
|
|
def test_build_web_agent_notification_events_registers_local_file(tmp_path):
|
|
"""Agent 工具发送本地文件时应生成可下载附件事件。"""
|
|
file_path = tmp_path / "report.txt"
|
|
file_path.write_text("hello", encoding="utf-8")
|
|
|
|
events = _build_web_agent_notification_events(
|
|
schemas.Notification(
|
|
channel=MessageChannel.WebAgent,
|
|
mtype=NotificationType.Agent,
|
|
file_path=str(file_path),
|
|
file_name="report.txt",
|
|
)
|
|
)
|
|
|
|
assert len(events) == 1
|
|
attachment = events[0]["attachment"]
|
|
assert events[0]["type"] == "attachment"
|
|
assert attachment["kind"] == "file"
|
|
assert attachment["name"] == "report.txt"
|
|
assert attachment["mime_type"] == "text/plain"
|
|
assert attachment["size"] == 5
|
|
assert attachment["url"].startswith("message/agent/file/")
|
|
|
|
|
|
def test_build_web_agent_notification_events_extracts_choice_card():
|
|
"""Agent 按钮通知应转换为 Web 选择卡片事件而非普通文本。"""
|
|
events = _build_web_agent_notification_events(
|
|
schemas.Notification(
|
|
channel=MessageChannel.WebAgent,
|
|
mtype=NotificationType.Agent,
|
|
title="需要你的选择",
|
|
text="请选择要执行的操作",
|
|
buttons=[
|
|
[
|
|
{
|
|
"text": "继续下载",
|
|
"callback_data": "agent_interaction:choice:req-1:1",
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"text": "查看详情",
|
|
"callback_data": "agent_interaction:choice:req-1:2",
|
|
}
|
|
],
|
|
],
|
|
)
|
|
)
|
|
|
|
assert events == [
|
|
{
|
|
"type": "choice",
|
|
"choice": {
|
|
"id": "req-1",
|
|
"title": "需要你的选择",
|
|
"prompt": "请选择要执行的操作",
|
|
"buttons": [
|
|
{
|
|
"label": "继续下载",
|
|
"callback_data": "agent_interaction:choice:req-1:1",
|
|
},
|
|
{
|
|
"label": "查看详情",
|
|
"callback_data": "agent_interaction:choice:req-1:2",
|
|
},
|
|
],
|
|
},
|
|
}
|
|
]
|
|
|
|
|
|
def test_resolve_web_agent_choice_payload_returns_next_message():
|
|
"""Web 按钮回调应解析为下一条用户消息并返回卡片反馈。"""
|
|
agent_interaction_manager.clear()
|
|
request = agent_interaction_manager.create_request(
|
|
session_id="web-agent:session",
|
|
user_id="1",
|
|
channel=MessageChannel.WebAgent.value,
|
|
source="web-agent",
|
|
username="admin",
|
|
title="需要你的选择",
|
|
prompt="请选择",
|
|
options=[
|
|
AgentInteractionOption(label="电影", value="我选择电影"),
|
|
AgentInteractionOption(label="电视剧", value="我选择电视剧"),
|
|
],
|
|
)
|
|
|
|
try:
|
|
result = _resolve_web_agent_choice_payload(
|
|
callback_data=f"agent_interaction:choice:{request.request_id}:2",
|
|
user_id="1",
|
|
)
|
|
finally:
|
|
agent_interaction_manager.clear()
|
|
|
|
assert result["message"] == "我选择电视剧"
|
|
assert result["session_id"] == "web-agent:session"
|
|
assert result["feedback"]["prompt"] == "请选择"
|
|
assert result["feedback"]["selected_label"] == "电视剧"
|