Route manual downloads through Arr collectors
Magent CI/CD / verify (push) Canceled after 3m43s
Magent CI/CD / deploy-prod (push) Canceled after 0s
Magent CI/CD / deploy-beta (push) Canceled after 0s

This commit is contained in:
2026-08-29 22:13:46 +12:00
parent 96fc43365f
commit 391cd41d71
6 changed files with 379 additions and 189 deletions
+174
View File
@@ -262,6 +262,180 @@ class LiveDownloadProgressTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(result["torrents"][0]["progressPercent"], 13.4)
class CollectorManualDownloadTests(unittest.IsolatedAsyncioTestCase):
@staticmethod
def _runtime() -> SimpleNamespace:
return SimpleNamespace(
jellyseerr_base_url=None,
jellyseerr_api_key=None,
sonarr_base_url="http://sonarr.test",
sonarr_api_key="sonarr-key",
radarr_base_url="http://radarr.test",
radarr_api_key="radarr-key",
)
async def test_tv_manual_search_uses_sonarr_and_keeps_season_packs(self) -> None:
snapshot = Snapshot(
request_id="3909",
title="Example Show",
request_type=RequestType.tv,
raw={"arr": {"item": {"id": 42}}},
)
sonarr = SimpleNamespace(
configured=lambda: True,
get_episodes=AsyncMock(
return_value=[
{"id": 101, "seasonNumber": 1, "monitored": True, "hasFile": False},
{"id": 201, "seasonNumber": 2, "monitored": True, "hasFile": False},
{"id": 202, "seasonNumber": 2, "monitored": True, "hasFile": True},
]
),
search_releases=AsyncMock(
side_effect=[
[
{
"title": "Example.Show.S01.1080p",
"guid": "season-one",
"indexerId": 7,
"indexer": "Prowlarr",
"protocol": "torrent",
"fullSeason": True,
"seasonNumber": 1,
}
],
[],
]
),
)
with patch.object(requests_router, "get_runtime_settings", return_value=self._runtime()), patch.object(
requests_router, "build_snapshot", new=AsyncMock(return_value=snapshot)
), patch.object(requests_router, "SonarrClient", return_value=sonarr), patch.object(
requests_router, "save_action"
):
result = await requests_router.action_search(
"3909", user={"username": "viewer", "role": "user"}
)
sonarr.search_releases.assert_any_await(42, 1)
sonarr.search_releases.assert_any_await(42, 2)
self.assertEqual(result["collector"], "Sonarr")
self.assertTrue(result["releases"][0]["fullSeason"])
self.assertEqual(result["releases"][0]["seasonNumber"], 1)
async def test_movie_manual_search_uses_radarr(self) -> None:
snapshot = Snapshot(
request_id="4000",
title="Example Movie",
request_type=RequestType.movie,
raw={"arr": {"item": {"id": 84}}},
)
radarr = SimpleNamespace(
configured=lambda: True,
search_releases=AsyncMock(
return_value=[
{
"title": "Example.Movie.2026.1080p",
"guid": "movie-release",
"indexerId": 9,
"indexer": "Prowlarr",
"protocol": "torrent",
}
]
),
)
with patch.object(requests_router, "get_runtime_settings", return_value=self._runtime()), patch.object(
requests_router, "build_snapshot", new=AsyncMock(return_value=snapshot)
), patch.object(requests_router, "RadarrClient", return_value=radarr), patch.object(
requests_router, "save_action"
):
result = await requests_router.action_search(
"4000", user={"username": "viewer", "role": "user"}
)
radarr.search_releases.assert_awaited_once_with(84)
self.assertEqual(result["collector"], "Radarr")
self.assertEqual(result["releases"][0]["guid"], "movie-release")
async def test_tv_manual_grab_is_sent_to_sonarr_not_qbittorrent(self) -> None:
snapshot = Snapshot(
request_id="3909",
title="Example Show",
request_type=RequestType.tv,
)
sonarr = SimpleNamespace(
configured=lambda: True,
grab_release=AsyncMock(return_value={"guid": "season-one", "indexerId": 7}),
push_release=AsyncMock(),
)
payload = {
"title": "Example.Show.S01.1080p",
"guid": "season-one",
"indexerId": 7,
"protocol": "torrent",
}
with patch.object(requests_router, "get_runtime_settings", return_value=self._runtime()), patch.object(
requests_router, "build_snapshot", new=AsyncMock(return_value=snapshot)
), patch.object(requests_router, "SonarrClient", return_value=sonarr), patch.object(
requests_router, "save_action"
):
result = await requests_router.action_grab(
"3909", payload, user={"username": "viewer", "role": "user"}
)
sonarr.grab_release.assert_awaited_once_with("season-one", 7)
sonarr.push_release.assert_not_awaited()
self.assertEqual(result["response"], {"collector": "Sonarr", "queued": True})
async def test_stale_movie_release_still_routes_through_radarr_push(self) -> None:
snapshot = Snapshot(
request_id="4000",
title="Example Movie",
request_type=RequestType.movie,
)
response = httpx.Response(
404,
request=httpx.Request("POST", "http://radarr.test/api/v3/release"),
json={"message": "release cache expired"},
)
cache_miss = httpx.HTTPStatusError(
"release cache expired",
request=response.request,
response=response,
)
radarr = SimpleNamespace(
configured=lambda: True,
grab_release=AsyncMock(side_effect=cache_miss),
push_release=AsyncMock(return_value=[{"approved": True, "downloadAllowed": True}]),
)
payload = {
"title": "Example.Movie.2026.1080p",
"guid": "stale-release",
"indexerId": 9,
"indexer": "Prowlarr",
"protocol": "torrent",
"publishDate": "2026-08-29T00:00:00Z",
"downloadUrl": "http://prowlarr.test/download/1",
}
with patch.object(requests_router, "get_runtime_settings", return_value=self._runtime()), patch.object(
requests_router, "build_snapshot", new=AsyncMock(return_value=snapshot)
), patch.object(requests_router, "RadarrClient", return_value=radarr), patch.object(
requests_router, "save_action"
):
result = await requests_router.action_grab(
"4000", payload, user={"username": "viewer", "role": "user"}
)
radarr.push_release.assert_awaited_once()
pushed = radarr.push_release.await_args.args[0]
self.assertEqual(pushed["downloadUrl"], "http://prowlarr.test/download/1")
self.assertEqual(pushed["protocol"], "torrent")
self.assertEqual(result["response"], {"collector": "Radarr", "queued": True})
class DatabaseEmailTests(TempDatabaseMixin, unittest.TestCase):
def test_set_user_email_is_case_insensitive(self) -> None:
created = db.create_user_if_missing(