@@ -773,3 +773,155 @@ def test_check_resets_current_priority_when_new_episodes_expand_target_range(sel
773773 self .assertEqual (subscribe .total_episode , 5 )
774774 self .assertEqual (subscribe .lack_episode , 2 )
775775 self .assertEqual (subscribe .current_priority , 0 )
776+
777+ def test_best_version_interested_episodes_excludes_same_priority (self ):
778+ """同 pri_order 的候选不应再把已达到该优先级的集列为可升级集。
779+
780+ 回归场景:E2 已记录在 episode_priority 中为 99,候选种子标题覆盖 E2/E3 且
781+ 其 pri_order=99;E2 不应进入 interested 集合,E3(None)则应进入。这是
782+ 洗版重复下载链路的源头判定,必须保持"严格大于"语义。
783+ """
784+ subscribe = self ._build_subscribe (
785+ total_episode = 3 ,
786+ episode_priority = {"1" : 100 , "2" : 99 },
787+ current_priority = 100 ,
788+ )
789+ context = SimpleNamespace (
790+ meta_info = SimpleNamespace (season_list = [1 ], episode_list = [2 , 3 ]),
791+ selected_episodes = None ,
792+ )
793+
794+ interested = SubscribeChain ._SubscribeChain__get_best_version_interested_episodes (
795+ subscribe = subscribe ,
796+ context = context ,
797+ priority = 99 ,
798+ )
799+
800+ self .assertEqual (interested , [3 ])
801+
802+ def test_best_version_interested_episodes_uses_title_episode_list_for_full_pack (self ):
803+ """整包候选(标题展开的集列表)只把仍可提升优先级的集纳入 interested。
804+
805+ 防回归场景:标题显示"第53-104集",实际目标范围只有 1..92,episode_priority
806+ 已经把 1..82 升到 100,E83 已经记到 99。同 pri_order=99 的同一资源再来时,
807+ interested 应只剩 [84..92],绝不能含 E83,否则后续下载层会再下一次同优先级。
808+ """
809+ subscribe = self ._build_subscribe (
810+ total_episode = 92 ,
811+ episode_priority = {
812+ ** {str (ep ): 100 for ep in range (1 , 83 )},
813+ "83" : 99 ,
814+ },
815+ current_priority = 99 ,
816+ )
817+ context = SimpleNamespace (
818+ meta_info = SimpleNamespace (season_list = [1 ], episode_list = list (range (53 , 105 ))),
819+ selected_episodes = None ,
820+ )
821+
822+ interested = SubscribeChain ._SubscribeChain__get_best_version_interested_episodes (
823+ subscribe = subscribe ,
824+ context = context ,
825+ priority = 99 ,
826+ )
827+
828+ self .assertEqual (interested , list (range (84 , 93 )))
829+
830+
831+ class SubscribeFilterAllowedEpisodesTest (TestCase ):
832+ """验证洗版过滤循环会把 interested 集合落到 context.allowed_episodes 上。
833+
834+ 这条用例直接覆盖回归点:当 __get_best_version_interested_episodes 返回非空
835+ 集合时,候选必须带着允许集进入下载层,下游 batch_download 才能在标题元数据
836+ 与实际种子文件错位时做出正确取舍。
837+ """
838+
839+ def _build_subscribe (self , ** overrides ):
840+ return SubscribeChainTest ()._build_subscribe (** overrides )
841+
842+ def test_filter_writes_allowed_episodes_to_context (self ):
843+ subscribe = self ._build_subscribe (
844+ total_episode = 92 ,
845+ episode_priority = {
846+ ** {str (ep ): 100 for ep in range (1 , 83 )},
847+ "83" : 99 ,
848+ },
849+ current_priority = 99 ,
850+ )
851+ context = SimpleNamespace (
852+ meta_info = SimpleNamespace (season_list = [1 ], episode_list = list (range (53 , 105 ))),
853+ selected_episodes = None ,
854+ )
855+
856+ interested = SubscribeChain ._SubscribeChain__get_best_version_interested_episodes (
857+ subscribe = subscribe ,
858+ context = context ,
859+ priority = 99 ,
860+ )
861+ # 复刻 subscribe.py 过滤循环中的赋值,确认结果作为允许集传递。
862+ context .allowed_episodes = set (interested ) if interested else None
863+
864+ self .assertIsNotNone (context .allowed_episodes )
865+ self .assertEqual (context .allowed_episodes , set (range (84 , 93 )))
866+ # 关键回归点:E83 已达到 99,不在允许集内;下游交集后即不会再下 E83。
867+ self .assertNotIn (83 , context .allowed_episodes )
868+
869+ def test_filter_leaves_allowed_episodes_none_when_no_upgrade (self ):
870+ """同 pri_order 且目标集均已达到该优先级时,候选不应被放行,
871+ 相应地也不会有 allowed_episodes 被写入。"""
872+ subscribe = self ._build_subscribe (
873+ total_episode = 3 ,
874+ episode_priority = {"1" : 100 , "2" : 99 , "3" : 99 },
875+ current_priority = 99 ,
876+ )
877+ context = SimpleNamespace (
878+ meta_info = SimpleNamespace (season_list = [1 ], episode_list = [2 , 3 ]),
879+ selected_episodes = None ,
880+ )
881+
882+ interested = SubscribeChain ._SubscribeChain__get_best_version_interested_episodes (
883+ subscribe = subscribe ,
884+ context = context ,
885+ priority = 99 ,
886+ )
887+
888+ self .assertEqual (interested , [])
889+
890+ def test_filter_writes_allowed_episodes_in_match_path (self ):
891+ """RSS/订阅刷新分支 match() 需要与 search() 对称地写入 allowed_episodes。
892+
893+ match() 路径下候选是 `_context = copy.copy(context)`,再走 best_version
894+ 判定。此用例复刻 match() 的过滤序列,验证浅拷贝后的 _context 在写入
895+ allowed_episodes 时不会污染原始 context,且写入结果与 search() 一致。
896+ 若 match() 分支漏写 allowed_episodes,下游 batch_download 将看不到允许集
897+ 约束,回归到 2c458317 之前的同优先级重复下载状态。
898+ """
899+ import copy
900+
901+ subscribe = self ._build_subscribe (
902+ total_episode = 92 ,
903+ episode_priority = {
904+ ** {str (ep ): 100 for ep in range (1 , 83 )},
905+ "83" : 99 ,
906+ },
907+ current_priority = 99 ,
908+ )
909+ original_context = SimpleNamespace (
910+ meta_info = SimpleNamespace (season_list = [1 ], episode_list = list (range (53 , 105 ))),
911+ selected_episodes = None ,
912+ allowed_episodes = None ,
913+ )
914+ _context = copy .copy (original_context )
915+
916+ interested = SubscribeChain ._SubscribeChain__get_best_version_interested_episodes (
917+ subscribe = subscribe ,
918+ context = _context ,
919+ priority = 99 ,
920+ )
921+ # 复刻 match() 中的赋值;search() 与 match() 必须保持同形以避免分支漏改。
922+ if interested :
923+ _context .allowed_episodes = set (interested )
924+
925+ self .assertEqual (_context .allowed_episodes , set (range (84 , 93 )))
926+ # 浅拷贝 + 新字段写入不应反向污染源 context(match() 中 contexts 缓存可能跨多次匹配复用)。
927+ self .assertIsNone (original_context .allowed_episodes )
0 commit comments