mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 08:26:53 +08:00
fix(agent): preserve episode lock on unchanged totals (#6271)
This commit is contained in:
@@ -213,6 +213,8 @@ class QuerySubscribesTool(MoviePilotTool):
|
|||||||
).model_dump(
|
).model_dump(
|
||||||
include=set(QUERY_SUBSCRIBE_OUTPUT_FIELDS), exclude_none=True
|
include=set(QUERY_SUBSCRIBE_OUTPUT_FIELDS), exclude_none=True
|
||||||
)
|
)
|
||||||
|
# 手动总集数是运行锁状态,不属于公共订阅写入 Schema,查询时直接从实体读取。
|
||||||
|
payload["manual_total_episode"] = subscribe.manual_total_episode or 0
|
||||||
payload["type"] = media_type_to_agent(payload.get("type"))
|
payload["type"] = media_type_to_agent(payload.get("type"))
|
||||||
if payload["type"] == "music" and not payload.get("music_type"):
|
if payload["type"] == "music" and not payload.get("music_type"):
|
||||||
payload["music_type"] = "recording"
|
payload["music_type"] = "recording"
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class UpdateSubscribeTool(MoviePilotTool):
|
|||||||
subscribe_dict["season"] = season
|
subscribe_dict["season"] = season
|
||||||
|
|
||||||
# 集数相关
|
# 集数相关
|
||||||
if total_episode is not None:
|
if total_episode is not None and total_episode != subscribe.total_episode:
|
||||||
subscribe_dict["total_episode"] = total_episode
|
subscribe_dict["total_episode"] = total_episode
|
||||||
# 如果总集数增加,缺失集数也要相应增加
|
# 如果总集数增加,缺失集数也要相应增加
|
||||||
if total_episode > (subscribe.total_episode or 0):
|
if total_episode > (subscribe.total_episode or 0):
|
||||||
@@ -346,6 +346,7 @@ class UpdateSubscribeTool(MoviePilotTool):
|
|||||||
"season": updated_subscribe.season,
|
"season": updated_subscribe.season,
|
||||||
"state": updated_subscribe.state,
|
"state": updated_subscribe.state,
|
||||||
"total_episode": updated_subscribe.total_episode,
|
"total_episode": updated_subscribe.total_episode,
|
||||||
|
"manual_total_episode": updated_subscribe.manual_total_episode,
|
||||||
"lack_episode": updated_subscribe.lack_episode,
|
"lack_episode": updated_subscribe.lack_episode,
|
||||||
"start_episode": updated_subscribe.start_episode,
|
"start_episode": updated_subscribe.start_episode,
|
||||||
"quality": updated_subscribe.quality,
|
"quality": updated_subscribe.quality,
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from app.agent.tools.impl.query_subscribes import QuerySubscribesTool
|
||||||
|
from app.db.models.subscribe import Subscribe
|
||||||
|
from app.schemas.types import MediaType
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_query_subscribes_returns_manual_total_episode():
|
||||||
|
"""订阅查询应返回手动总集数标记,供 Agent 判断元数据是否可自动刷新。"""
|
||||||
|
subscribe = Subscribe(
|
||||||
|
id=160,
|
||||||
|
name="测试剧集",
|
||||||
|
type=MediaType.TV.value,
|
||||||
|
tmdbid=224839,
|
||||||
|
season=1,
|
||||||
|
total_episode=175,
|
||||||
|
manual_total_episode=1,
|
||||||
|
state="P",
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"app.agent.tools.impl.query_subscribes.SubscribeOper.async_list",
|
||||||
|
return_value=[subscribe],
|
||||||
|
):
|
||||||
|
result = asyncio.run(
|
||||||
|
QuerySubscribesTool(session_id="session-1", user_id="10001").run(
|
||||||
|
tmdb_id=224839,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
_, result_json = result.split("\n\n", maxsplit=1)
|
||||||
|
payload = json.loads(result_json)
|
||||||
|
assert payload[0]["manual_total_episode"] == 1
|
||||||
@@ -3,7 +3,7 @@ import json
|
|||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
from app.agent.tools.impl.update_subscribe import UpdateSubscribeTool
|
from app.agent.tools.impl.update_subscribe import UpdateSubscribeTool
|
||||||
from app.schemas.types import EventType
|
from app.schemas.types import EventType, MediaType
|
||||||
|
|
||||||
|
|
||||||
def test_agent_update_subscribe_sends_modified_event_payload_with_agent_scene():
|
def test_agent_update_subscribe_sends_modified_event_payload_with_agent_scene():
|
||||||
@@ -41,6 +41,120 @@ def test_agent_update_subscribe_sends_modified_event_payload_with_agent_scene():
|
|||||||
assert event_payload["subscribe_info"]["name"] == "新标题"
|
assert event_payload["subscribe_info"]["name"] == "新标题"
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_update_subscribe_ignores_unchanged_total_episode():
|
||||||
|
"""Agent 回传相同总集数时,不应产生数据库写入或订阅调整事件。"""
|
||||||
|
subscribe = _AgentSubscribe(
|
||||||
|
id=160,
|
||||||
|
name="测试剧集",
|
||||||
|
type=MediaType.TV.value,
|
||||||
|
state="R",
|
||||||
|
total_episode=175,
|
||||||
|
lack_episode=0,
|
||||||
|
manual_total_episode=0,
|
||||||
|
)
|
||||||
|
oper = _SubscribeOperStub(subscribe)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"app.agent.tools.impl.update_subscribe.SubscribeOper",
|
||||||
|
return_value=oper,
|
||||||
|
), patch(
|
||||||
|
"app.agent.tools.impl.update_subscribe.eventmanager.async_send_event",
|
||||||
|
new=AsyncMock(),
|
||||||
|
) as send_event:
|
||||||
|
result = asyncio.run(
|
||||||
|
UpdateSubscribeTool(session_id="session-1", user_id="10001").run(
|
||||||
|
subscribe_id=160,
|
||||||
|
total_episode=175,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = json.loads(result)
|
||||||
|
assert payload == {"success": False, "message": "没有提供要更新的字段"}
|
||||||
|
assert oper.updates == []
|
||||||
|
send_event.assert_not_awaited()
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_update_subscribe_only_updates_other_fields_with_unchanged_total_episode():
|
||||||
|
"""Agent 同时回传相同总集数和洗版设置时,只更新实际请求的其他字段。"""
|
||||||
|
subscribe = _AgentSubscribe(
|
||||||
|
id=160,
|
||||||
|
name="测试剧集",
|
||||||
|
type=MediaType.TV.value,
|
||||||
|
state="R",
|
||||||
|
total_episode=175,
|
||||||
|
lack_episode=0,
|
||||||
|
manual_total_episode=0,
|
||||||
|
best_version=0,
|
||||||
|
)
|
||||||
|
oper = _SubscribeOperStub(subscribe)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"app.agent.tools.impl.update_subscribe.SubscribeOper",
|
||||||
|
return_value=oper,
|
||||||
|
), patch(
|
||||||
|
"app.agent.tools.impl.update_subscribe.eventmanager.async_send_event",
|
||||||
|
new=AsyncMock(),
|
||||||
|
) as send_event:
|
||||||
|
result = asyncio.run(
|
||||||
|
UpdateSubscribeTool(session_id="session-1", user_id="10001").run(
|
||||||
|
subscribe_id=160,
|
||||||
|
total_episode=175,
|
||||||
|
best_version=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = json.loads(result)
|
||||||
|
assert payload["success"] is True
|
||||||
|
assert payload["updated_fields"] == ["best_version"]
|
||||||
|
assert payload["subscribe"]["manual_total_episode"] == 0
|
||||||
|
assert oper.updates == [(160, {"best_version": 1})]
|
||||||
|
send_event.assert_awaited_once()
|
||||||
|
_, event_payload = send_event.await_args.args
|
||||||
|
assert event_payload["fields"] == ["best_version"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_agent_update_subscribe_marks_changed_total_episode_as_manual():
|
||||||
|
"""Agent 真正修改总集数时,保持 Web API 的手动总集数语义。"""
|
||||||
|
subscribe = _AgentSubscribe(
|
||||||
|
id=160,
|
||||||
|
name="测试剧集",
|
||||||
|
type=MediaType.TV.value,
|
||||||
|
state="R",
|
||||||
|
total_episode=175,
|
||||||
|
lack_episode=0,
|
||||||
|
manual_total_episode=0,
|
||||||
|
)
|
||||||
|
oper = _SubscribeOperStub(subscribe)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"app.agent.tools.impl.update_subscribe.SubscribeOper",
|
||||||
|
return_value=oper,
|
||||||
|
), patch(
|
||||||
|
"app.agent.tools.impl.update_subscribe.eventmanager.async_send_event",
|
||||||
|
new=AsyncMock(),
|
||||||
|
):
|
||||||
|
result = asyncio.run(
|
||||||
|
UpdateSubscribeTool(session_id="session-1", user_id="10001").run(
|
||||||
|
subscribe_id=160,
|
||||||
|
total_episode=190,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = json.loads(result)
|
||||||
|
assert payload["success"] is True
|
||||||
|
assert payload["subscribe"]["manual_total_episode"] == 1
|
||||||
|
assert oper.updates == [
|
||||||
|
(
|
||||||
|
160,
|
||||||
|
{
|
||||||
|
"total_episode": 190,
|
||||||
|
"lack_episode": 15,
|
||||||
|
"manual_total_episode": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class _AgentSubscribe:
|
class _AgentSubscribe:
|
||||||
"""
|
"""
|
||||||
最小订阅替身,模拟 Agent 工具依赖的订阅对象接口。
|
最小订阅替身,模拟 Agent 工具依赖的订阅对象接口。
|
||||||
|
|||||||
Reference in New Issue
Block a user