Files
MoviePilot/tests/test_rss_url_validation.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

116 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
验证RSS 地址校验及访问安全边界。
"""
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
import app.chain.torrents as torrents_module
from app.application.rss import RssHelper, configure_rss_ports, reset_rss_ports
from app.chain.torrents import TorrentsChain
@pytest.fixture
def rss_ports():
"""
装配可记录调用的 RSS 端口,确保用例不产生真实网络请求。
"""
http_port = Mock()
parser_port = Mock()
parser_port.parse.return_value = None
configure_rss_ports(http=http_port, browser=Mock(), parser=parser_port)
yield http_port
reset_rss_ports()
@pytest.mark.parametrize(
"url",
[None, "", " ", 123, b"https://example.com/rss", "#", "#fragment",
"/rss", "rss.xml", "ftp://example.com/rss", "https://bad host/rss",
"https://example.com:abc/rss", "https://example.com:65536/rss"],
)
def test_rss_helper_rejects_invalid_urls_before_http_request(url, rss_ports):
"""
无效 RSS 地址应返回普通错误,并在端口层之前被拒绝。
"""
assert RssHelper().parse(url) is False
rss_ports.get.assert_not_called()
def test_rss_helper_strips_whitespace_and_allows_http_fragment(rss_ports):
"""
合法 HTTP(S) 地址即使带 fragment 也应去除外层空白后请求。
"""
rss_ports.get.return_value = SimpleNamespace(
status_code=200,
content=b"<rss />",
text="<rss />",
reason="OK",
)
rss_ports.decode_xml.return_value = "<rss />"
assert RssHelper().parse(" http://example.com:8080/rss#item\n") == []
assert rss_ports.get.call_args.kwargs["url"] == "http://example.com:8080/rss#item"
@pytest.mark.parametrize("parse_result, renew_expected", [(False, False), (None, True)])
def test_torrents_rss_preserves_false_and_none_contract(
monkeypatch, parse_result, renew_expected, rss_ports
):
"""
RSS 普通错误与过期结果应继续分别对应不续期和自动续期。
"""
site = {
"id": 1,
"name": "测试站点",
"rss": "https://example.com/rss",
"proxy": False,
"timeout": 30,
"ua": None,
}
sites_helper = Mock()
sites_helper.get_indexer.return_value = site
monkeypatch.setattr(torrents_module, "SitesHelper", lambda: sites_helper)
monkeypatch.setattr(RssHelper, "parse", lambda *_args, **_kwargs: parse_result)
chain = TorrentsChain()
renew = Mock()
monkeypatch.setattr(chain, "_TorrentsChain__renew_rss_url", renew)
assert chain.rss("example.com") == []
assert renew.called is renew_expected
def test_torrents_rss_warns_once_and_skips_invalid_site_url(monkeypatch, rss_ports):
"""
站点 RSS 配置无效时只记录简短告警,不请求或自动续期。
"""
site = {
"id": 1,
"name": "测试站点",
"rss": "not-a-url?passkey=secret",
"proxy": False,
"timeout": 30,
"ua": None,
}
sites_helper = Mock()
sites_helper.get_indexer.return_value = site
warnings = []
errors = []
monkeypatch.setattr(torrents_module, "SitesHelper", lambda: sites_helper)
monkeypatch.setattr(torrents_module.logger, "warning", warnings.append)
monkeypatch.setattr(torrents_module.logger, "error", errors.append)
chain = TorrentsChain()
renew = Mock()
monkeypatch.setattr(chain, "_TorrentsChain__renew_rss_url", renew)
assert chain.rss("example.com") == []
assert rss_ports.get.call_count == 0
assert errors == []
assert warnings == ["站点 example.com RSS地址无效,跳过获取"]
assert "passkey=secret" not in warnings[0]
renew.assert_not_called()