151 lines
5.4 KiB
Python
Executable File
151 lines
5.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Focused offline tests for the model-oriented Sonarr overlay."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
SOURCE = Path(__file__).parents[1] / "platform/mcp/patches/mcp_sonarr.py"
|
|
|
|
|
|
def load_module():
|
|
async def run_blocking(function, *args, **kwargs):
|
|
kwargs.pop("service", None)
|
|
return function(*args, **kwargs)
|
|
|
|
def dispatch(client, action, kwargs, **_options):
|
|
return getattr(client, action)(**kwargs)
|
|
|
|
utilities = types.ModuleType("agent_utilities.mcp_utilities")
|
|
utilities.dispatch = dispatch
|
|
utilities.run_blocking = run_blocking
|
|
sys.modules["agent_utilities"] = types.ModuleType("agent_utilities")
|
|
sys.modules["agent_utilities.mcp_utilities"] = utilities
|
|
fastmcp = types.ModuleType("fastmcp")
|
|
fastmcp.FastMCP = object
|
|
sys.modules["fastmcp"] = fastmcp
|
|
pydantic = types.ModuleType("pydantic")
|
|
pydantic.Field = lambda *args, **kwargs: kwargs.get("default")
|
|
sys.modules["pydantic"] = pydantic
|
|
auth = types.ModuleType("arr_mcp.auth")
|
|
auth.get_sonarr_client = lambda: None
|
|
sys.modules["arr_mcp"] = types.ModuleType("arr_mcp")
|
|
sys.modules["arr_mcp.auth"] = auth
|
|
spec = importlib.util.spec_from_file_location("sonarr_patch", SOURCE)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec.loader
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
class FakeSonarrClient:
|
|
def __init__(self, *, rejected: bool = False) -> None:
|
|
self.posted = []
|
|
self.release = {
|
|
"guid": "exact-guid",
|
|
"title": "Murder.She.Wrote.S07.German.AC3D.DL.1080p.WebHD.x265-FuN",
|
|
"indexer": "Test Indexer",
|
|
"indexerId": 7,
|
|
"size": 27_600_000_000,
|
|
"protocol": "usenet",
|
|
"downloadAllowed": not rejected,
|
|
"releaseGroup": "FuN",
|
|
"seasonNumber": 7,
|
|
"fullSeason": True,
|
|
"rejections": ["Existing file has equal or better quality"] if rejected else [],
|
|
}
|
|
|
|
def get_release(self, **_kwargs):
|
|
return [dict(self.release)]
|
|
|
|
def get_episode(self, **_kwargs):
|
|
return [
|
|
{"id": 1, "seasonNumber": 7, "episodeNumber": 1, "hasFile": True},
|
|
{"id": 2, "seasonNumber": 7, "episodeNumber": 2, "hasFile": False},
|
|
]
|
|
|
|
def post_release(self, data=None, **kwargs):
|
|
payload = data if data is not None else kwargs
|
|
self.posted.append(payload)
|
|
return payload
|
|
|
|
|
|
class ReleaseGrabTests(unittest.IsolatedAsyncioTestCase):
|
|
def setUp(self) -> None:
|
|
self.module = load_module()
|
|
self.module._APPROVALS.clear()
|
|
|
|
async def test_exact_release_requires_preview_and_ticket(self) -> None:
|
|
client = FakeSonarrClient()
|
|
scope = {"series_id": 42, "season_number": 7, "guid": "exact-guid"}
|
|
preview = await self.module._preview_release_grab(client, scope)
|
|
self.assertFalse(preview["download_started"])
|
|
self.assertEqual(preview["existing_episode_files_in_season"], 1)
|
|
self.assertTrue(preview["approval_ticket"])
|
|
|
|
result = await self.module._grab_release(
|
|
client,
|
|
{**scope, "confirm": True, "approval_ticket": preview["approval_ticket"]},
|
|
)
|
|
self.assertTrue(result["download_started"])
|
|
self.assertFalse(result["replacement_guaranteed"])
|
|
self.assertEqual(len(client.posted), 1)
|
|
self.assertEqual(client.posted[0]["guid"], "exact-guid")
|
|
|
|
async def test_rejected_release_needs_force_in_preview(self) -> None:
|
|
client = FakeSonarrClient(rejected=True)
|
|
scope = {"series_id": 42, "season_number": 7, "guid": "exact-guid"}
|
|
blocked = await self.module._preview_release_grab(client, scope)
|
|
self.assertTrue(blocked["force_required"])
|
|
self.assertIsNone(blocked["approval_ticket"])
|
|
|
|
approved = await self.module._preview_release_grab(client, {**scope, "force": True})
|
|
self.assertFalse(approved["force_required"])
|
|
self.assertTrue(approved["approval_ticket"])
|
|
|
|
async def test_guid_must_still_match_current_sonarr_results(self) -> None:
|
|
client = FakeSonarrClient()
|
|
with self.assertRaisesRegex(ValueError, "no longer present"):
|
|
await self.module._preview_release_grab(
|
|
client,
|
|
{"series_id": 42, "season_number": 7, "guid": "different-guid"},
|
|
)
|
|
|
|
async def test_search_can_limit_results_to_group_and_season_pack(self) -> None:
|
|
client = FakeSonarrClient()
|
|
client.get_release = lambda **_kwargs: [
|
|
dict(client.release),
|
|
{
|
|
**client.release,
|
|
"guid": "episode-guid",
|
|
"title": "Mord.ist.ihr.Hobby.S07E02.German.1080p-FuN",
|
|
"fullSeason": False,
|
|
},
|
|
{
|
|
**client.release,
|
|
"guid": "other-group",
|
|
"title": "Mord.ist.ihr.Hobby.S07.German.1080p-HQC",
|
|
"releaseGroup": "HQC",
|
|
},
|
|
]
|
|
result = await self.module._search_releases(
|
|
client,
|
|
{
|
|
"series_id": 42,
|
|
"season_number": 7,
|
|
"release_group": "FuN",
|
|
"season_pack_only": True,
|
|
},
|
|
)
|
|
self.assertEqual(result["results"]["total"], 1)
|
|
self.assertEqual(result["results"]["items"][0]["guid"], "exact-guid")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|