"""Tests for tmdb.py against a mocked httpx transport — no live network in CI.""" import httpx import pytest from meshbay_node.tmdb import TmdbClient class FakeRoster: def __init__(self, token: str | None = "fake-token", language: str | None = None): self._token = token self._language = language async def tmdb_config(self): return self._token, self._language def _handler(response_map): def handle(request: httpx.Request) -> httpx.Response: path = request.url.path for prefix, body in response_map.items(): if path.endswith(prefix): return httpx.Response(200, json=body) return httpx.Response(404, json={"results": []}) return handle @pytest.mark.asyncio async def test_search_movie_returns_top_result_and_confidence(): body = {"results": [{"id": 42, "title": "The Great Adventure", "release_date": "2015-01-01"}]} client = TmdbClient( roster=FakeRoster(), transport=httpx.MockTransport(_handler({"search/movie": body})), ) result, ratio = await client.search_movie("The Great Adventure", 2015) assert result is not None assert result["id"] == 42 assert ratio > 0.9 await client.close() # ── V9: year-exact preference, only when the top hit is weak ──────────────── @pytest.mark.asyncio async def test_year_exact_result_wins_when_the_top_hit_is_low_confidence(): # results[0] is TMDB's popularity #1 but a poor textual match for the # query; results[2] is the exact requested year. body = {"results": [ {"id": 1, "title": "Franchise vs. The Doctor", "release_date": "1962-10-05"}, {"id": 2, "title": "Franchise: Goldfinger", "release_date": "1964-09-17"}, {"id": 3, "title": "Second Errand", "release_date": "2002-11-20"}, ]} client = TmdbClient( roster=FakeRoster(), transport=httpx.MockTransport(_handler({"search/movie": body})), ) result, _ = await client.search_movie("The Franchise", 2002) assert result["id"] == 3 await client.close() @pytest.mark.asyncio async def test_year_is_ignored_when_the_top_hit_is_already_confident(): body = {"results": [ {"id": 1, "title": "The Franchise", "release_date": "1999-01-01"}, {"id": 2, "title": "Unrelated", "release_date": "2002-01-01"}, ]} client = TmdbClient( roster=FakeRoster(), transport=httpx.MockTransport(_handler({"search/movie": body})), ) result, ratio = await client.search_movie("The Franchise", 2002) assert result["id"] == 1 and ratio > 0.9 await client.close() @pytest.mark.asyncio async def test_no_year_match_leaves_the_top_result_in_place(): body = {"results": [ {"id": 1, "title": "Something Else Entirely", "release_date": "1990-01-01"}, {"id": 2, "title": "Also Not It", "release_date": "1991-01-01"}, ]} client = TmdbClient( roster=FakeRoster(), transport=httpx.MockTransport(_handler({"search/movie": body})), ) result, _ = await client.search_movie("The Franchise", 2002) assert result["id"] == 1 await client.close() @pytest.mark.asyncio async def test_search_tv_returns_top_result(): body = {"results": [{"id": 7, "name": "Some Show"}]} client = TmdbClient( roster=FakeRoster(), transport=httpx.MockTransport(_handler({"search/tv": body})), ) result, ratio = await client.search_tv("Some Show") assert result is not None and result["id"] == 7 assert ratio > 0.9 await client.close() @pytest.mark.asyncio async def test_configured_language_is_sent_to_tmdb(): captured = {} def handle(request: httpx.Request) -> httpx.Response: captured["language"] = request.url.params.get("language") return httpx.Response(200, json={"results": []}) client = TmdbClient( roster=FakeRoster(language="fr-FR"), transport=httpx.MockTransport(handle), ) await client.search_movie("Anything") assert captured["language"] == "fr-FR" await client.close() @pytest.mark.asyncio async def test_no_language_configured_omits_the_param(): captured = {} def handle(request: httpx.Request) -> httpx.Response: captured["has_language"] = "language" in request.url.params return httpx.Response(200, json={"results": []}) client = TmdbClient( roster=FakeRoster(language=None), transport=httpx.MockTransport(handle), ) await client.search_movie("Anything") assert captured["has_language"] is False await client.close() @pytest.mark.asyncio async def test_no_results_returns_none_and_zero_confidence(): client = TmdbClient( roster=FakeRoster(), transport=httpx.MockTransport(_handler({"search/movie": {"results": []}})), ) result, ratio = await client.search_movie("Nonexistent Obscure Title") assert result is None assert ratio == 0.0 await client.close() @pytest.mark.asyncio async def test_no_token_resolvable_makes_no_request(monkeypatch): monkeypatch.delenv("MESHBAY_TMDB_DEFAULT_TOKEN", raising=False) calls = [] def handle(request: httpx.Request) -> httpx.Response: calls.append(request) return httpx.Response(200, json={"results": []}) client = TmdbClient( roster=FakeRoster(token=None), transport=httpx.MockTransport(handle), ) result, ratio = await client.search_movie("Anything") assert result is None assert calls == [] await client.close() @pytest.mark.asyncio async def test_http_error_returns_none_gracefully(): def handle(request: httpx.Request) -> httpx.Response: return httpx.Response(500, json={"status_message": "server error"}) client = TmdbClient( roster=FakeRoster(), transport=httpx.MockTransport(handle), ) result, ratio = await client.search_movie("Anything") assert result is None assert ratio == 0.0 await client.close() @pytest.mark.asyncio async def test_poster_url_builds_full_url(): assert TmdbClient.poster_url("/abc123.jpg") == "https://image.tmdb.org/t/p/w500/abc123.jpg" assert TmdbClient.poster_url(None) is None