mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
fix(indexer): adapt Rousi Pro PeerGo personal API key
This commit is contained in:
+213
-3
@@ -1,22 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=no-name-in-module
|
||||
|
||||
import base64
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from app.chain.site import SiteChain
|
||||
from app.modules.indexer.parser.rousi import RousiSiteUserInfo
|
||||
from app.modules.indexer.spider import rousi as rousi_module
|
||||
from app.modules.indexer.spider.rousi import RousiSpider
|
||||
from app.schemas import MediaType
|
||||
|
||||
|
||||
def _build_indexer() -> dict:
|
||||
class _FakeResponse:
|
||||
"""构造 PeerGo 兼容 API 测试使用的最小响应对象。"""
|
||||
|
||||
def __init__(self, payload: dict, status_code: int = 200):
|
||||
"""保存响应数据和状态码。"""
|
||||
self._payload = payload
|
||||
self.status_code = status_code
|
||||
self.reason = "OK"
|
||||
|
||||
def json(self) -> dict:
|
||||
"""返回预设 JSON 数据。"""
|
||||
return self._payload
|
||||
|
||||
|
||||
def _build_indexer(apikey: str = "rousi-secret", proxy: bool = False) -> dict:
|
||||
"""构造 Rousi Pro API Spider 所需的最小站点配置。"""
|
||||
return {
|
||||
"id": "rousipro",
|
||||
"name": "Rousi Pro",
|
||||
"domain": "https://rousi.pro/",
|
||||
"apikey": "rousi-secret",
|
||||
"apikey": apikey,
|
||||
"ua": "MoviePilot-Test",
|
||||
"proxy": False,
|
||||
"proxy": proxy,
|
||||
}
|
||||
|
||||
|
||||
@@ -54,3 +74,193 @@ def test_parse_result_marks_music_torrents(rousi_spider):
|
||||
MediaType.MOVIE.value,
|
||||
MediaType.TV.value,
|
||||
]
|
||||
|
||||
|
||||
def test_search_uses_peergo_personal_api_key_contract(monkeypatch):
|
||||
"""搜索应使用个人 API Key 兼容响应并生成短时下载地址换取请求。"""
|
||||
captured = {}
|
||||
payload = {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"page": 1,
|
||||
"page_size": 100,
|
||||
"total": 1,
|
||||
"total_pages": 1,
|
||||
"torrents": [{
|
||||
"category": "movie",
|
||||
"category_name": "电影",
|
||||
"created_at": "2026-06-09T12:04:15.184279Z",
|
||||
"downloads": 21,
|
||||
"id": 8461,
|
||||
"leechers": 6,
|
||||
"promotion": {
|
||||
"down_multiplier": 0,
|
||||
"is_active": True,
|
||||
"until": "2026-09-21T16:36:31.008958Z",
|
||||
"up_multiplier": 2,
|
||||
},
|
||||
"seeders": 31,
|
||||
"size": 190438784128,
|
||||
"subtitle": "电影中文副标题",
|
||||
"title": "Movie.2026.2160p.UHD.BluRay",
|
||||
"uuid": "8461",
|
||||
}],
|
||||
},
|
||||
}
|
||||
|
||||
def fake_get_res(request, url: str, params: dict = None, **_kwargs):
|
||||
"""记录搜索请求并回放 PeerGo MoviePilot 兼容响应。"""
|
||||
captured.update({"url": url, "params": params, "headers": request._headers})
|
||||
return _FakeResponse(payload)
|
||||
|
||||
monkeypatch.setattr(rousi_module, "get_configured_system_config", lambda: None)
|
||||
monkeypatch.setattr(rousi_module, "get_runtime_setting", lambda _key: {"https": "proxy"})
|
||||
monkeypatch.setattr(rousi_module.RequestUtils, "get_res", fake_get_res)
|
||||
|
||||
error, torrents = RousiSpider(_build_indexer(proxy=True)).search(
|
||||
keyword="Movie",
|
||||
mtype=MediaType.MOVIE,
|
||||
page=0,
|
||||
)
|
||||
|
||||
assert not error
|
||||
assert captured == {
|
||||
"url": "https://rousi.pro/api/v1/torrents",
|
||||
"params": {
|
||||
"page": 1,
|
||||
"page_size": 100,
|
||||
"keyword": "Movie",
|
||||
"category": "movie",
|
||||
},
|
||||
"headers": {
|
||||
"Authorization": "Bearer rousi-secret",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
assert len(torrents) == 1
|
||||
assert torrents[0]["title"] == "Movie.2026.2160p.UHD.BluRay"
|
||||
assert torrents[0]["page_url"] == "https://rousi.pro/torrents/8461"
|
||||
assert torrents[0]["downloadvolumefactor"] == 0
|
||||
assert torrents[0]["uploadvolumefactor"] == 2
|
||||
assert torrents[0]["category"] == MediaType.MOVIE.value
|
||||
|
||||
encoded_config, detail_url = torrents[0]["enclosure"].split("]", 1)
|
||||
request_config = json.loads(base64.b64decode(encoded_config[1:]).decode("utf-8"))
|
||||
assert detail_url == "https://rousi.pro/api/v1/torrents/8461"
|
||||
assert request_config == {
|
||||
"method": "get",
|
||||
"cookie": False,
|
||||
"header": {
|
||||
"Authorization": "Bearer rousi-secret",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
"proxy": True,
|
||||
"result": "data.download_url",
|
||||
}
|
||||
|
||||
|
||||
def test_search_rejects_missing_personal_api_key(monkeypatch):
|
||||
"""未配置个人 API Key 时不得向 PeerGo 发起搜索请求。"""
|
||||
monkeypatch.setattr(rousi_module, "get_configured_system_config", lambda: None)
|
||||
spider = RousiSpider(_build_indexer(apikey=""))
|
||||
|
||||
error, torrents = spider.search(keyword="Movie")
|
||||
|
||||
assert error
|
||||
assert torrents == []
|
||||
|
||||
|
||||
def test_user_parser_reads_peergo_profile_with_personal_api_key(monkeypatch):
|
||||
"""用户数据解析应通过个人 API Key 读取 PeerGo 兼容资料结构。"""
|
||||
captured = {}
|
||||
profile = {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 619,
|
||||
"username": "jxxghp",
|
||||
"level_text": "Lv.1",
|
||||
"registered_at": "2026-01-05T19:50:59.012Z",
|
||||
"uploaded": 1099511627776,
|
||||
"downloaded": 536870912000,
|
||||
"ratio": 2.048,
|
||||
"karma": 1079960,
|
||||
"seeding_leeching_data": {
|
||||
"seeding_count": 8,
|
||||
"seeding_size": 2147483648000,
|
||||
"leeching_count": 1,
|
||||
"leeching_size": 10737418240,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def fake_get_page_content(_self, url: str, headers: dict = None, **_kwargs):
|
||||
"""记录用户资料请求并回放 PeerGo 兼容响应。"""
|
||||
captured.update({"url": url, "headers": headers})
|
||||
return json.dumps(profile)
|
||||
|
||||
monkeypatch.setattr(RousiSiteUserInfo, "_get_page_content", fake_get_page_content)
|
||||
parser = RousiSiteUserInfo(
|
||||
site_name="Rousi Pro",
|
||||
url="https://rousi.pro/",
|
||||
site_cookie="",
|
||||
apikey="rousi-secret",
|
||||
token=None,
|
||||
ua="MoviePilot-Test",
|
||||
)
|
||||
|
||||
parser.parse()
|
||||
|
||||
assert captured["url"] == "https://rousi.pro/api/v1/profile"
|
||||
assert captured["headers"]["Authorization"] == "Bearer rousi-secret"
|
||||
assert parser.userid == 619
|
||||
assert parser.username == "jxxghp"
|
||||
assert parser.user_level == "Lv.1"
|
||||
assert parser.join_at == "2026-01-05 19:50:59"
|
||||
assert parser.upload == 1099511627776
|
||||
assert parser.download == 536870912000
|
||||
assert parser.ratio == 2.05
|
||||
assert parser.bonus == 1079960
|
||||
assert parser.seeding == 8
|
||||
assert parser.seeding_size == 2147483648000
|
||||
assert parser.leeching == 1
|
||||
assert parser.leeching_size == 10737418240
|
||||
|
||||
|
||||
def test_site_connectivity_uses_peergo_personal_api_key(monkeypatch):
|
||||
"""Rousi 连接测试应以 Bearer 个人 API Key 请求兼容资料接口。"""
|
||||
captured = {}
|
||||
|
||||
def fake_get_res(request, url: str, **_kwargs):
|
||||
"""记录连接测试请求并返回有效用户资料。"""
|
||||
captured.update({"url": url, "headers": request._headers})
|
||||
return _FakeResponse({"code": 0, "message": "success", "data": {"id": 619}})
|
||||
|
||||
monkeypatch.setattr("app.chain.site.RequestUtils.get_res", fake_get_res)
|
||||
site = SimpleNamespace(
|
||||
url="https://rousi.pro/",
|
||||
apikey="rousi-secret",
|
||||
proxy=0,
|
||||
timeout=15,
|
||||
)
|
||||
chain = object.__new__(SiteChain)
|
||||
chain.runtime_config = SimpleNamespace(proxy=None)
|
||||
|
||||
state, message = chain._SiteChain__rousi_test(site)
|
||||
|
||||
assert state
|
||||
assert message == "连接成功"
|
||||
assert captured["url"] == "https://rousi.pro/api/v1/profile"
|
||||
assert captured["headers"]["Authorization"] == "Bearer rousi-secret"
|
||||
|
||||
|
||||
def test_site_connectivity_rejects_missing_personal_api_key():
|
||||
"""Rousi 连接测试应在请求前报告个人 API Key 缺失。"""
|
||||
site = SimpleNamespace(url="https://rousi.pro/", apikey="", proxy=0, timeout=15)
|
||||
chain = object.__new__(SiteChain)
|
||||
|
||||
state, message = chain._SiteChain__rousi_test(site)
|
||||
|
||||
assert not state
|
||||
assert message == "未配置个人 API Key"
|
||||
|
||||
Reference in New Issue
Block a user