diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index 83756d64b..6ece5d67a 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -120,6 +120,9 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]): if not torrent_from_file and not is_magnet: return None, None, None, f"添加种子任务失败:无法读取种子文件" + # 失败提示使用种子名称(磁力链接保留原文),不拼接种子文件的二进制内容 + torrent_desc = torrent_from_file.name if torrent_from_file else content + # 获取下载器 server: Qbittorrent = self.get_instance(downloader) if not server: @@ -176,7 +179,7 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]): finally: torrents.clear() del torrents - return None, None, None, f"添加种子任务失败:{content}" + return None, None, None, f"添加种子任务失败:{torrent_desc}" else: # 获取种子Hash torrent_hash = next(iter(added_torrent_ids), None) @@ -185,7 +188,7 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]): if torrent_hash: temporary_tag_cleaned = server.delete_torrents_tag(torrent_hash, tag) if not torrent_hash: - return None, None, None, f"下载任务添加成功,但获取Qbittorrent任务信息失败:{content}" + return None, None, None, f"下载任务添加成功,但获取Qbittorrent任务信息失败:{torrent_desc}" else: # 获取种子内容布局: `Original: 原始, Subfolder: 创建子文件夹, NoSubfolder: 不创建子文件夹` torrent_layout = server.get_content_layout() diff --git a/app/modules/rtorrent/__init__.py b/app/modules/rtorrent/__init__.py index 086f696f7..663c4fd75 100644 --- a/app/modules/rtorrent/__init__.py +++ b/app/modules/rtorrent/__init__.py @@ -102,6 +102,9 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]): if not torrent_from_file and not is_magnet: return None, None, None, f"添加种子任务失败:无法读取种子文件" + # 失败提示使用种子名称(磁力链接保留原文),不拼接种子文件的二进制内容 + torrent_desc = torrent_from_file.name if torrent_from_file else content + # 获取下载器 server: Rtorrent = self.get_instance(downloader) if not server: @@ -176,7 +179,7 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]): finally: torrents.clear() del torrents - return None, None, None, f"添加种子任务失败:{content}" + return None, None, None, f"添加种子任务失败:{torrent_desc}" else: # 获取种子Hash torrent_hash = server.get_torrent_id_by_tag(tags=tag) @@ -185,7 +188,7 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]): None, None, None, - f"下载任务添加成功,但获取rTorrent任务信息失败:{content}", + f"下载任务添加成功,但获取rTorrent任务信息失败:{torrent_desc}", ) else: if is_paused: diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index 97a40c1d2..c146b7bbd 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -99,6 +99,9 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]): if not torrent_from_file and not is_magnet: return None, None, None, f"添加种子任务失败:无法读取种子文件" + # 失败提示使用种子名称(磁力链接保留原文),不拼接种子文件的二进制内容 + torrent_desc = torrent_from_file.name if torrent_from_file else content + # 获取下载器 server: Transmission = self.get_instance(downloader) if not server: @@ -153,7 +156,7 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]): finally: torrents.clear() del torrents - return None, None, None, f"添加种子任务失败:{content}" + return None, None, None, f"添加种子任务失败:{torrent_desc}" else: torrent_hash = added_torrent.hashString if is_paused: diff --git a/tests/test_agent_evaluation_live.py b/tests/test_agent_evaluation_live.py index d298fb6fe..92d1eb0ca 100644 --- a/tests/test_agent_evaluation_live.py +++ b/tests/test_agent_evaluation_live.py @@ -234,8 +234,17 @@ async def test_chat_completions_provider_uses_google_compatible_request_options( assert "reasoning" not in parameters -def test_official_google_provider_uses_native_tool_transport(): +def test_official_google_provider_uses_native_tool_transport(monkeypatch): """官方 Gemini 主机必须走原生 SDK,避免 OpenAI 兼容层丢失 thought_signature。""" + import langchain_google_genai.chat_models as google_models + + # 生产补丁会修改 SDK 模块级解析函数;测试结束时恢复,避免污染后续模型参数测试。 + for name in ("_is_gemini_3_or_later", "_parse_chat_history"): + monkeypatch.setattr(google_models, name, getattr(google_models, name)) + monkeypatch.setattr( + google_models, "_thought_signature_patched", + getattr(google_models, "_thought_signature_patched", False), raising=False, + ) settings = ModelSettings( model="gemini-3.1-pro-preview", base_url="https://generativelanguage.googleapis.com/v1beta", diff --git a/tests/test_downloader_add_failure_message.py b/tests/test_downloader_add_failure_message.py new file mode 100644 index 000000000..67df116af --- /dev/null +++ b/tests/test_downloader_add_failure_message.py @@ -0,0 +1,76 @@ +"""下载器添加任务失败时的提示文本。""" + +from pathlib import Path +from unittest.mock import MagicMock + +import pytest + +from app.modules.qbittorrent import QbittorrentModule +from app.modules.rtorrent import RtorrentModule +from app.modules.transmission import TransmissionModule + +_ANNOUNCE = b"https://tracker.invalid/announce?passkey=0123456789abcdef" +_PIECES = bytes(range(20)) +_TORRENT = ( + b"d8:announce" + str(len(_ANNOUNCE)).encode() + b":" + _ANNOUNCE + + b"4:infod6:lengthi1024e4:name8:test.mkv12:piece lengthi16384e" + + b"6:pieces" + str(len(_PIECES)).encode() + b":" + _PIECES + b"ee" +) +_MAGNET = "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567" + + +def _build_module(module_cls, server): + """构造只包含下载所需依赖的下载器模块。""" + module = module_cls.__new__(module_cls) + module.get_instance = MagicMock(return_value=server) + module.normalize_path = MagicMock(side_effect=lambda path, _downloader: str(path)) + module.get_default_config_name = MagicMock(return_value="default") + return module + + +def _rejecting_server(module_cls): + """下载器拒绝添加,且没有同名同大小的已有任务。""" + server = MagicMock() + if module_cls is QbittorrentModule: + server.add_torrent.return_value = (False, []) + elif module_cls is TransmissionModule: + server.add_torrent.return_value = None + else: + server.add_torrent.return_value = False + server.get_torrents.return_value = ([], False) + return server + + +@pytest.mark.parametrize("module_cls", [QbittorrentModule, TransmissionModule, RtorrentModule]) +@pytest.mark.parametrize( + ("content", "expected"), + [(_TORRENT, "添加种子任务失败:test.mkv"), (_MAGNET, f"添加种子任务失败:{_MAGNET}")], + ids=["torrent", "magnet"], +) +def test_rejected_download_reports_torrent_name_or_magnet(module_cls, content, expected): + """种子文件被拒时提示种子名称,磁力链接保留原文,不拼接种子二进制内容。""" + module = _build_module(module_cls, _rejecting_server(module_cls)) + + result = module.download(content=content, download_dir=Path("/downloads"), cookie="") + + assert result == (None, None, None, expected) + + +@pytest.mark.parametrize( + ("module_cls", "expected"), + [ + (QbittorrentModule, "下载任务添加成功,但获取Qbittorrent任务信息失败:test.mkv"), + (RtorrentModule, "下载任务添加成功,但获取rTorrent任务信息失败:test.mkv"), + ], + ids=["qbittorrent", "rtorrent"], +) +def test_added_download_without_hash_reports_torrent_name(module_cls, expected): + """添加成功但查不到任务时,提示同样使用种子名称。""" + server = MagicMock() + server.add_torrent.return_value = (True, []) if module_cls is QbittorrentModule else True + server.get_torrent_id_by_tag.return_value = None + module = _build_module(module_cls, server) + + result = module.download(content=_TORRENT, download_dir=Path("/downloads"), cookie="") + + assert result == (None, None, None, expected)