Fix NFO creation 500 error for missing folders

- Auto-create series folder if it doesn't exist
- Add unit and integration tests for folder creation
- NFO creation now works for newly added series
This commit is contained in:
2026-01-18 12:07:37 +01:00
parent 9877f9400c
commit 4e56093ff9
4 changed files with 261 additions and 2 deletions

View File

@@ -119,10 +119,32 @@ For each task completed:
## TODO List:
All tasks completed! The NFO database query issue has been resolved.
All tasks completed! Recent issues have been resolved.
## Recently Fixed Issues:
### ✅ Fixed: NFO Creation 500 Error - Missing Folder (2026-01-18)
**Issue:** POST http://127.0.0.1:8000/api/nfo/blue-period/create returns 500 (Internal Server Error) with message "Series folder not found: /home/lukas/Volume/serien/Blue Period"
**Root Cause:** The NFO service was checking if the series folder existed before creating NFO files, and raising a FileNotFoundError if it didn't. This prevented users from creating NFO files for newly added series where no episodes had been downloaded yet.
**Solution:** Modified `create_tvshow_nfo()` in `nfo_service.py` to automatically create the series folder (with `mkdir(parents=True, exist_ok=True)`) if it doesn't exist, instead of raising an error.
**Files Modified:**
- [src/core/services/nfo_service.py](../src/core/services/nfo_service.py)
**Tests Added:**
- [tests/unit/test_nfo_service_folder_creation.py](../tests/unit/test_nfo_service_folder_creation.py) - Unit test verifying folder creation
- [tests/integration/test_nfo_folder_creation.py](../tests/integration/test_nfo_folder_creation.py) - Integration test for end-to-end verification
**Verification:**
- Unit and integration tests pass
- NFO files can now be created for series even when the folder doesn't exist
- Folder is automatically created when needed
---
### ✅ Fixed: NFO Service 503 Error (2026-01-18)
**Issue:** Failed to load resource: the server responded with a status of 503 (Service Unavailable) when creating NFO files.
@@ -132,6 +154,7 @@ All tasks completed! The NFO database query issue has been resolved.
**Solution:** Updated `fastapi_app.py` startup code to sync NFO configuration (including TMDB API key) from `data/config.json` to `settings` object, similar to how `anime_directory` was already being synced.
**Files Modified:**
- [src/server/fastapi_app.py](../src/server/fastapi_app.py)
**Verification:** NFO endpoints now return 200 OK instead of 503, and NFO creation is functional.

View File

@@ -100,7 +100,8 @@ class NFOService:
folder_path = self.anime_directory / serie_folder
if not folder_path.exists():
raise FileNotFoundError(f"Series folder not found: {folder_path}")
logger.info(f"Creating series folder: {folder_path}")
folder_path.mkdir(parents=True, exist_ok=True)
async with self.tmdb_client:
# Search for TV show

View File

@@ -0,0 +1,114 @@
"""Integration test for NFO creation with missing folder.
Tests that NFO creation works when the series folder doesn't exist yet.
"""
from pathlib import Path
from unittest.mock import AsyncMock, patch
import pytest
from src.core.services.nfo_service import NFOService
from src.core.services.tmdb_client import TMDBClient
@pytest.mark.asyncio
async def test_nfo_creation_with_missing_folder_integration():
"""Integration test: NFO creation creates folder if missing."""
# Use actual temp directory for this test
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
anime_dir = Path(tmpdir)
serie_folder = "Test Anime Series"
folder_path = anime_dir / serie_folder
# Verify folder doesn't exist
assert not folder_path.exists()
# Create NFO service
nfo_service = NFOService(
tmdb_api_key="test_key",
anime_directory=str(anime_dir),
image_size="original"
)
# Mock TMDB responses
mock_search = {
"results": [{
"id": 99999,
"name": "Test Anime Series",
"first_air_date": "2023-01-01",
"overview": "Test",
"vote_average": 8.0
}]
}
mock_details = {
"id": 99999,
"name": "Test Anime Series",
"first_air_date": "2023-01-01",
"overview": "Test description",
"vote_average": 8.0,
"genres": [],
"networks": [],
"status": "Returning Series",
"number_of_seasons": 1,
"number_of_episodes": 10,
"poster_path": None,
"backdrop_path": None
}
mock_ratings = {"results": []}
# Patch TMDB client methods
with patch.object(
nfo_service.tmdb_client, 'search_tv_show',
new_callable=AsyncMock
) as mock_search_method, \
patch.object(
nfo_service.tmdb_client, 'get_tv_show_details',
new_callable=AsyncMock
) as mock_details_method, \
patch.object(
nfo_service.tmdb_client, 'get_tv_show_content_ratings',
new_callable=AsyncMock
) as mock_ratings_method, \
patch.object(
nfo_service, '_download_media_files',
new_callable=AsyncMock
) as mock_download:
mock_search_method.return_value = mock_search
mock_details_method.return_value = mock_details
mock_ratings_method.return_value = mock_ratings
mock_download.return_value = {
"poster": False,
"logo": False,
"fanart": False
}
# Create NFO - this should create the folder
nfo_path = await nfo_service.create_tvshow_nfo(
serie_name="Test Anime Series",
serie_folder=serie_folder,
year=2023,
download_poster=False,
download_logo=False,
download_fanart=False
)
# Verify folder was created
assert folder_path.exists(), "Series folder should have been created"
assert folder_path.is_dir(), "Series folder should be a directory"
# Verify NFO file exists
assert nfo_path.exists(), "NFO file should exist"
assert nfo_path.name == "tvshow.nfo", "NFO file should be named tvshow.nfo"
assert nfo_path.parent == folder_path, "NFO should be in series folder"
# Verify NFO file has content
nfo_content = nfo_path.read_text()
assert "<tvshow>" in nfo_content, "NFO should contain tvshow tag"
assert "<title>Test Anime Series</title>" in nfo_content, "NFO should contain title"
print(f"✓ Test passed: Folder created at {folder_path}")
print(f"✓ NFO file created at {nfo_path}")

View File

@@ -0,0 +1,121 @@
"""Unit tests for NFO service folder creation.
Tests that the NFO service correctly creates series folders when they don't exist.
"""
import tempfile
from pathlib import Path
from unittest.mock import AsyncMock, patch
import pytest
from src.core.services.nfo_service import NFOService
class TestNFOServiceFolderCreation:
"""Test NFO service creates folders when needed."""
@pytest.fixture
def temp_anime_dir(self):
"""Create temporary anime directory."""
with tempfile.TemporaryDirectory() as tmpdir:
yield Path(tmpdir)
@pytest.fixture
def nfo_service(self, temp_anime_dir):
"""Create NFO service with temporary directory."""
return NFOService(
tmdb_api_key="test_api_key",
anime_directory=str(temp_anime_dir),
image_size="original",
auto_create=False
)
@pytest.mark.asyncio
async def test_create_nfo_creates_missing_folder(
self, nfo_service, temp_anime_dir
):
"""Test that create_tvshow_nfo creates folder if it doesn't exist."""
serie_folder = "Test Series"
folder_path = temp_anime_dir / serie_folder
# Verify folder doesn't exist initially
assert not folder_path.exists()
# Mock TMDB client responses
mock_search_results = {
"results": [
{
"id": 12345,
"name": "Test Series",
"first_air_date": "2023-01-01",
"overview": "Test overview",
"vote_average": 8.5
}
]
}
mock_details = {
"id": 12345,
"name": "Test Series",
"first_air_date": "2023-01-01",
"overview": "Test overview",
"vote_average": 8.5,
"genres": [{"id": 16, "name": "Animation"}],
"networks": [{"name": "Test Network"}],
"status": "Returning Series",
"number_of_seasons": 1,
"number_of_episodes": 12,
"poster_path": "/test_poster.jpg",
"backdrop_path": "/test_backdrop.jpg"
}
mock_content_ratings = {
"results": [
{"iso_3166_1": "DE", "rating": "12"}
]
}
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_call, \
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
) as mock_download:
mock_search.return_value = mock_search_results
mock_details_call.return_value = mock_details
mock_ratings.return_value = mock_content_ratings
mock_download.return_value = {
"poster": False,
"logo": False,
"fanart": False
}
# Call create_tvshow_nfo
nfo_path = await nfo_service.create_tvshow_nfo(
serie_name="Test Series",
serie_folder=serie_folder,
year=2023,
download_poster=False,
download_logo=False,
download_fanart=False
)
# Verify folder was created
assert folder_path.exists()
assert folder_path.is_dir()
# Verify NFO file was created
assert nfo_path.exists()
assert nfo_path.name == "tvshow.nfo"
assert nfo_path.parent == folder_path