diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index 9b66d832..fb73db6c 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -1186,6 +1186,7 @@ class SubscribeChain(ChainBase): ) mediakey = subscribe.tmdbid or subscribe.doubanid custom_word_list = subscribe.custom_words.split("\n") if subscribe.custom_words else None + search_attempted = False # 校验当前时间减订阅创建时间是否大于1分钟,否则跳过先,留出编辑订阅的时间 if subscribe.date: now = datetime.now() @@ -1203,6 +1204,7 @@ class SubscribeChain(ChainBase): ) time.sleep(sleep_time) try: + search_attempted = True logger.info(f'开始搜索订阅,标题:{subscribe.name} ...') try: meta = build_subscribe_meta(subscribe) @@ -1346,7 +1348,7 @@ class SubscribeChain(ChainBase): downloads=downloads, lefts=lefts) finally: # 如果状态为N则更新为R - if subscribe and subscribe.state == 'N': + if search_attempted and subscribe and subscribe.state == 'N': subscribeoper.update(subscribe.id, {'state': 'R'}) if progress_callback: progress_callback( diff --git a/tests/test_subscribe_search_state.py b/tests/test_subscribe_search_state.py new file mode 100644 index 00000000..a8e981ee --- /dev/null +++ b/tests/test_subscribe_search_state.py @@ -0,0 +1,85 @@ +from datetime import datetime, timedelta +from types import SimpleNamespace +from unittest.mock import patch + +from app.chain import subscribe as subscribe_module +from app.chain.subscribe import SubscribeChain +from app.schemas.types import MediaType + + +class _SubscribeOper: + """ + 最小订阅 Oper 替身,隔离订阅搜索状态流转测试的数据库访问。 + """ + + subscribe = None + updates = [] + + def get(self, sid: int): + """ + 按 ID 返回测试订阅对象。 + """ + return self.subscribe if self.subscribe and self.subscribe.id == sid else None + + def list(self, _state: str): + """ + 返回批量搜索需要的测试订阅列表。 + """ + return [self.subscribe] if self.subscribe else [] + + def update(self, sid: int, payload: dict) -> None: + """ + 记录订阅状态更新请求。 + """ + self.updates.append((sid, payload)) + + +def _new_subscribe(created_at: datetime) -> SimpleNamespace: + """ + 构造一个新建电影订阅。 + """ + return SimpleNamespace( + id=31, + name="测试电影", + year="2026", + type=MediaType.MOVIE.value, + tmdbid=12345, + doubanid=None, + season=None, + custom_words=None, + date=created_at.strftime("%Y-%m-%d %H:%M:%S"), + state="N", + episode_group=None, + ) + + +def test_new_subscribe_search_keeps_state_when_recently_created(monkeypatch) -> None: + """ + 新增 60 秒保护期内跳过搜索时,应保留 N 状态等待下一轮新增订阅搜索。 + """ + _SubscribeOper.subscribe = _new_subscribe(datetime.now()) + _SubscribeOper.updates = [] + monkeypatch.setattr(subscribe_module, "SubscribeOper", _SubscribeOper) + + with patch.object(SubscribeChain, "recognize_media", return_value=None) as recognize: + chain = object.__new__(SubscribeChain) + chain.search(state="N", manual=False) + + recognize.assert_not_called() + assert _SubscribeOper.updates == [] + + +def test_new_subscribe_search_marks_state_after_attempt(monkeypatch) -> None: + """ + 新增订阅越过保护期并实际尝试搜索后,应从 N 状态收敛为 R。 + """ + _SubscribeOper.subscribe = _new_subscribe(datetime.now() - timedelta(minutes=2)) + _SubscribeOper.updates = [] + monkeypatch.setattr(subscribe_module, "SubscribeOper", _SubscribeOper) + + with patch.object(SubscribeChain, "recognize_media", return_value=None) as recognize: + chain = object.__new__(SubscribeChain) + chain.search(state="N", manual=False) + + recognize.assert_called_once() + assert _SubscribeOper.updates == [(31, {"state": "R"})]