Skip to content

Commit 22dac1c

Browse files
authored
test: move enrichment helper unit tests (#110)
* test: isolate enrichment helper tests * chore: fix loader test lint issues
1 parent 6907e8e commit 22dac1c

File tree

5 files changed

+208
-216
lines changed

5 files changed

+208
-216
lines changed

docker/pyproject.deps.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "mcp-plex"
3-
version = "0.26.76"
3+
version = "0.26.78"
44
requires-python = ">=3.11,<3.13"
55
dependencies = [
66
"fastmcp>=2.11.2",

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "mcp-plex"
7-
version = "0.26.76"
7+
version = "0.26.78"
88

99
description = "Plex-Oriented Model Context Protocol Server"
1010
requires-python = ">=3.11,<3.13"

tests/test_enrichment_helpers.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import asyncio
2+
import types
3+
4+
import httpx
5+
6+
from mcp_plex.loader.pipeline.enrichment import (
7+
_build_plex_item,
8+
_extract_external_ids,
9+
_fetch_tmdb_episode,
10+
_fetch_tmdb_movie,
11+
_fetch_tmdb_show,
12+
resolve_tmdb_season_number,
13+
)
14+
from mcp_plex.common.types import (
15+
TMDBSeason,
16+
TMDBShow,
17+
)
18+
19+
20+
def test_extract_external_ids():
21+
guid_objs = [
22+
types.SimpleNamespace(id="imdb://tt0133093"),
23+
types.SimpleNamespace(id="tmdb://603"),
24+
]
25+
item = types.SimpleNamespace(guids=guid_objs)
26+
ids = _extract_external_ids(item)
27+
assert ids.imdb == "tt0133093"
28+
assert ids.tmdb == "603"
29+
30+
31+
def test_extract_external_ids_missing_values():
32+
item = types.SimpleNamespace(guids=None)
33+
ids = _extract_external_ids(item)
34+
assert ids.imdb is None
35+
assert ids.tmdb is None
36+
37+
38+
def test_build_plex_item_handles_full_metadata():
39+
guid_objs = [
40+
types.SimpleNamespace(id="imdb://tt0133093"),
41+
types.SimpleNamespace(id="tmdb://603"),
42+
]
43+
raw = types.SimpleNamespace(
44+
ratingKey="603",
45+
guid="plex://movie/603",
46+
type="movie",
47+
title="The Matrix",
48+
summary="A hacker discovers the nature of his reality.",
49+
year=1999,
50+
guids=guid_objs,
51+
thumb="matrix.jpg",
52+
art="matrix_art.jpg",
53+
tagline="Welcome to the Real World",
54+
contentRating="R",
55+
directors=[types.SimpleNamespace(id=1, tag="Lana Wachowski", thumb="lana.jpg")],
56+
writers=[types.SimpleNamespace(id=2, tag="Lilly Wachowski", thumb="lilly.jpg")],
57+
actors=[types.SimpleNamespace(id=3, tag="Keanu Reeves", thumb="neo.jpg", role="Neo")],
58+
)
59+
60+
item = _build_plex_item(raw)
61+
assert item.rating_key == "603"
62+
assert item.directors[0].tag == "Lana Wachowski"
63+
assert item.actors[0].role == "Neo"
64+
65+
66+
def test_build_plex_item_missing_metadata_defaults():
67+
raw = types.SimpleNamespace(ratingKey="1", guid="g", type="movie", title="T")
68+
item = _build_plex_item(raw)
69+
assert item.directors == []
70+
assert item.writers == []
71+
assert item.actors == []
72+
73+
74+
def test_build_plex_item_converts_string_indices():
75+
raw = types.SimpleNamespace(
76+
ratingKey="1",
77+
guid="g",
78+
type="episode",
79+
title="Episode",
80+
parentIndex="02",
81+
index="03",
82+
)
83+
84+
item = _build_plex_item(raw)
85+
assert item.season_number == 2
86+
assert item.episode_number == 3
87+
88+
89+
def test_fetch_functions_success_and_failure():
90+
async def tmdb_movie_mock(request):
91+
assert request.headers.get("Authorization") == "Bearer k"
92+
if "good" in str(request.url):
93+
return httpx.Response(200, json={"id": 1, "title": "M"})
94+
return httpx.Response(404)
95+
96+
async def tmdb_show_mock(request):
97+
assert request.headers.get("Authorization") == "Bearer k"
98+
if "good" in str(request.url):
99+
return httpx.Response(200, json={"id": 1, "name": "S"})
100+
return httpx.Response(404)
101+
102+
async def tmdb_episode_mock(request):
103+
assert request.headers.get("Authorization") == "Bearer k"
104+
if "/tv/1/season/2/episode/3" in str(request.url):
105+
return httpx.Response(200, json={"id": 1, "name": "E"})
106+
return httpx.Response(404)
107+
108+
async def main():
109+
movie_transport = httpx.MockTransport(tmdb_movie_mock)
110+
show_transport = httpx.MockTransport(tmdb_show_mock)
111+
episode_transport = httpx.MockTransport(tmdb_episode_mock)
112+
113+
async with httpx.AsyncClient(transport=movie_transport) as client:
114+
assert (await _fetch_tmdb_movie(client, "good", "k")) is not None
115+
assert (await _fetch_tmdb_movie(client, "bad", "k")) is None
116+
117+
async with httpx.AsyncClient(transport=show_transport) as client:
118+
assert (await _fetch_tmdb_show(client, "good", "k")) is not None
119+
assert (await _fetch_tmdb_show(client, "bad", "k")) is None
120+
121+
async with httpx.AsyncClient(transport=episode_transport) as client:
122+
assert (await _fetch_tmdb_episode(client, 1, 2, 3, "k")) is not None
123+
assert (await _fetch_tmdb_episode(client, 1, 2, 4, "k")) is None
124+
125+
asyncio.run(main())
126+
127+
128+
def test_fetch_functions_handle_http_error():
129+
def raise_error(request: httpx.Request) -> httpx.Response: # type: ignore[override]
130+
raise httpx.ConnectError("boom", request=request)
131+
132+
async def main() -> None:
133+
transport = httpx.MockTransport(raise_error)
134+
async with httpx.AsyncClient(transport=transport) as client:
135+
assert await _fetch_tmdb_movie(client, "1", "k") is None
136+
async with httpx.AsyncClient(transport=transport) as client:
137+
assert await _fetch_tmdb_show(client, "1", "k") is None
138+
async with httpx.AsyncClient(transport=transport) as client:
139+
assert await _fetch_tmdb_episode(client, 1, 1, 1, "k") is None
140+
141+
asyncio.run(main())
142+
143+
144+
def test_resolve_tmdb_season_number_matches_name():
145+
episode = types.SimpleNamespace(parentIndex=2018, parentTitle="2018")
146+
show = TMDBShow(
147+
id=1,
148+
name="Show",
149+
seasons=[TMDBSeason(season_number=14, name="2018")],
150+
)
151+
assert resolve_tmdb_season_number(show, episode) == 14
152+
153+
154+
def test_resolve_tmdb_season_number_matches_air_date():
155+
episode = types.SimpleNamespace(parentIndex=2018, parentTitle="Season 2018")
156+
show = TMDBShow(
157+
id=1,
158+
name="Show",
159+
seasons=[TMDBSeason(season_number=16, name="Season 16", air_date="2018-01-03")],
160+
)
161+
assert resolve_tmdb_season_number(show, episode) == 16
162+
163+
164+
def test_resolve_tmdb_season_number_parent_year_fallback():
165+
episode = types.SimpleNamespace(
166+
parentIndex="Special",
167+
parentTitle="Special",
168+
parentYear=2018,
169+
)
170+
show = TMDBShow(
171+
id=1,
172+
name="Show",
173+
seasons=[TMDBSeason(season_number=5, name="Season 5", air_date="2018-06-01")],
174+
)
175+
assert resolve_tmdb_season_number(show, episode) == 5
176+
177+
178+
def test_resolve_tmdb_season_number_numeric_match():
179+
episode = types.SimpleNamespace(parentIndex=2, parentTitle="Season 2")
180+
show = TMDBShow(
181+
id=1,
182+
name="Show",
183+
seasons=[TMDBSeason(season_number=2, name="Season 2")],
184+
)
185+
assert resolve_tmdb_season_number(show, episode) == 2
186+
187+
188+
def test_resolve_tmdb_season_number_title_year():
189+
episode = types.SimpleNamespace(parentTitle="2018")
190+
show = TMDBShow(
191+
id=1,
192+
name="Show",
193+
seasons=[TMDBSeason(season_number=7, name="Season 7", air_date="2018-02-03")],
194+
)
195+
assert resolve_tmdb_season_number(show, episode) == 7
196+
197+
198+
def test_resolve_tmdb_season_number_parent_index_str():
199+
episode = types.SimpleNamespace(parentIndex="3")
200+
assert resolve_tmdb_season_number(None, episode) == 3
201+
202+
203+
def test_resolve_tmdb_season_number_parent_title_digit():
204+
episode = types.SimpleNamespace(parentTitle="4")
205+
assert resolve_tmdb_season_number(None, episode) == 4

0 commit comments

Comments
 (0)