fix: ensure all NFO properties are written on creation

- Add showtitle and namedseason to mapper output
- Add multi-language fallback (en-US, ja-JP) for empty overview
- Use search result overview as last resort fallback
- Add tests for new NFO creation behavior
This commit is contained in:
2026-03-06 21:20:17 +01:00
parent b34ee59bca
commit 69b409f42d
4 changed files with 294 additions and 32 deletions

View File

@@ -1385,3 +1385,152 @@ class TestYearExtractionComprehensive:
assert clean_name == "Series (12345)"
assert year is None
class TestEnrichFallbackLanguages:
"""Tests for multi-language fallback and search overview fallback."""
@pytest.mark.asyncio
async def test_japanese_fallback_when_english_also_empty(
self, nfo_service, tmp_path,
):
"""ja-JP fallback is tried when both de-DE and en-US are empty."""
series_folder = tmp_path / "Rare Anime"
series_folder.mkdir()
de_data = {
"id": 55555, "name": "Rare Anime",
"original_name": "レアアニメ", "first_air_date": "2024-01-01",
"overview": "",
"vote_average": 7.0, "vote_count": 50,
"status": "Continuing", "episode_run_time": [24],
"genres": [], "networks": [], "production_countries": [],
"poster_path": None, "backdrop_path": None,
"external_ids": {}, "credits": {"cast": []},
"images": {"logos": []},
}
en_data = {"id": 55555, "overview": ""}
ja_data = {"id": 55555, "overview": "日本語のあらすじ"}
async def side_effect(tv_id, **kwargs):
lang = kwargs.get("language")
if lang == "ja-JP":
return ja_data
if lang == "en-US":
return en_data
return de_data
with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \
patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
patch.object(nfo_service, '_download_media_files', new_callable=AsyncMock):
mock_search.return_value = {
"results": [{"id": 55555, "name": "Rare Anime", "first_air_date": "2024-01-01"}],
}
mock_details.side_effect = side_effect
mock_ratings.return_value = {"results": []}
nfo_path = await nfo_service.create_tvshow_nfo(
"Rare Anime", "Rare Anime",
download_poster=False, download_logo=False, download_fanart=False,
)
content = nfo_path.read_text(encoding="utf-8")
assert "<plot>日本語のあらすじ</plot>" in content
@pytest.mark.asyncio
async def test_search_overview_fallback_when_all_languages_empty(
self, nfo_service, tmp_path,
):
"""Search result overview is used as last resort."""
series_folder = tmp_path / "Brand New Anime"
series_folder.mkdir()
empty_data = {
"id": 77777, "name": "Brand New Anime",
"original_name": "新しいアニメ", "first_air_date": "2025-01-01",
"overview": "",
"vote_average": 0, "vote_count": 0,
"status": "Continuing", "episode_run_time": [],
"genres": [], "networks": [], "production_countries": [],
"poster_path": None, "backdrop_path": None,
"external_ids": {}, "credits": {"cast": []},
"images": {"logos": []},
}
async def side_effect(tv_id, **kwargs):
return empty_data
with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \
patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
patch.object(nfo_service, '_download_media_files', new_callable=AsyncMock):
# Search result DOES have an overview
mock_search.return_value = {
"results": [{
"id": 77777,
"name": "Brand New Anime",
"first_air_date": "2025-01-01",
"overview": "Search result overview text.",
}],
}
mock_details.side_effect = side_effect
mock_ratings.return_value = {"results": []}
nfo_path = await nfo_service.create_tvshow_nfo(
"Brand New Anime", "Brand New Anime",
download_poster=False, download_logo=False, download_fanart=False,
)
content = nfo_path.read_text(encoding="utf-8")
assert "<plot>Search result overview text.</plot>" in content
@pytest.mark.asyncio
async def test_no_japanese_fallback_when_english_succeeds(
self, nfo_service, tmp_path,
):
"""Stop after en-US if it provides the overview."""
series_folder = tmp_path / "Test Anime"
series_folder.mkdir()
de_data = {
"id": 88888, "name": "Test Anime",
"original_name": "テスト", "first_air_date": "2024-01-01",
"overview": "",
"vote_average": 7.0, "vote_count": 50,
"status": "Continuing", "episode_run_time": [24],
"genres": [], "networks": [], "production_countries": [],
"poster_path": None, "backdrop_path": None,
"external_ids": {}, "credits": {"cast": []},
"images": {"logos": []},
}
en_data = {"id": 88888, "overview": "English overview."}
async def side_effect(tv_id, **kwargs):
lang = kwargs.get("language")
if lang == "en-US":
return en_data
return de_data
with patch.object(nfo_service.tmdb_client, 'search_tv_show', new_callable=AsyncMock) as mock_search, \
patch.object(nfo_service.tmdb_client, 'get_tv_show_details', new_callable=AsyncMock) as mock_details, \
patch.object(nfo_service.tmdb_client, 'get_tv_show_content_ratings', new_callable=AsyncMock) as mock_ratings, \
patch.object(nfo_service, '_download_media_files', new_callable=AsyncMock):
mock_search.return_value = {
"results": [{"id": 88888, "name": "Test Anime", "first_air_date": "2024-01-01"}],
}
mock_details.side_effect = side_effect
mock_ratings.return_value = {"results": []}
nfo_path = await nfo_service.create_tvshow_nfo(
"Test Anime", "Test Anime",
download_poster=False, download_logo=False, download_fanart=False,
)
content = nfo_path.read_text(encoding="utf-8")
assert "<plot>English overview.</plot>" in content
# de-DE + en-US = 2 calls (no ja-JP needed)
assert mock_details.call_count == 2