From 6aec2a17337fa8b3192a345b624466e7b9fe98ea Mon Sep 17 00:00:00 2001 From: Lukas Date: Fri, 5 Jun 2026 20:42:26 +0200 Subject: [PATCH] docs: add SetupService to architecture, update changelog and testing docs - ARCHITECTURE.md: add setup_service.py to services list - CHANGELOG.md: add Unreleased section with folder scan key resolution fix - TESTING.md: add SetupService testing section with example tests --- docs/ARCHITECTURE.md | 1 + docs/CHANGELOG.md | 11 +++++++++ docs/TESTING.md | 55 ++++++++++++++++++-------------------------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index be27dbc..4891ea5 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -81,6 +81,7 @@ src/server/ | +-- websocket_service.py# WebSocket broadcasting | +-- queue_repository.py # Database persistence | +-- nfo_service.py # NFO metadata management +| +-- setup_service.py # Series key resolution from folder names | +-- folder_scan_service.py # Daily folder maintenance scan +-- models/ # Pydantic models | +-- auth.py # Auth request/response models diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7622c43..5b77386 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -37,6 +37,17 @@ This changelog follows [Keep a Changelog](https://keepachangelog.com/) principle --- +## [Unreleased] - 2026-06-05 + +### Fixed + +- **Folder scan series key resolution**: Fixed "Could not resolve series key for folder, skipping" warnings during library setup. `_resolve_key_via_search()` now uses fuzzy title matching instead of exact string comparison. + - Added `_normalize_title()` to strip anime suffixes: `(TV)`, `(Anime)`, `(OAD)`, `(OVA)`, `(Special)`, `(Movie)`, `(Spin-Off)` + - Added `_titles_match()` using `difflib.SequenceMatcher` with 0.85 similarity threshold for tolerance of minor title variations + - Added debug logging for title mismatches and multiple search results + +--- + ## [1.3.1] - 2026-02-22 ### Added diff --git a/docs/TESTING.md b/docs/TESTING.md index 4beec9f..d923887 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -73,40 +73,31 @@ from src.server.models.download import DownloadItem, EpisodeIdentifier class MockQueueRepository: def __init__(self): self._items: Dict[str, DownloadItem] = {} - - async def save_item(self, item: DownloadItem) -> DownloadItem: - self._items[item.id] = item - return item - - async def get_item(self, item_id: str) -> Optional[DownloadItem]: - return self._items.get(item_id) - - async def get_all_items(self) -> List[DownloadItem]: - return list(self._items.values()) - - async def set_error(self, item_id: str, error: str) -> bool: - if item_id in self._items: - self._items[item_id].error = error - return True - return False - - async def delete_item(self, item_id: str) -> bool: - if item_id in self._items: - del self._items[item_id] - return True - return False - - async def clear_all(self) -> int: - count = len(self._items) - self._items.clear() - return count ``` -**Key points:** -- The mock uses in-memory storage, no database required -- All async methods are implemented (even if just pass-through) -- `save_item` uses `item.id` as key (must be set before calling) -- Suitable for unit tests only (no persistence) +### Testing SetupService + +SetupService handles series key resolution from folder names during library setup. Test file: `tests/unit/test_setup_service.py`. + +Key methods tested: +- `_extract_year_from_folder_name()` — parses `(YYYY)` suffix +- `_extract_title_from_folder_name()` — strips year suffix +- `_resolve_key_via_search()` — resolves provider key via fuzzy title matching + +```python +@pytest.mark.asyncio +async def test_returns_key_when_single_exact_match(self): + """Search returns 1 result with same name → returns key.""" + mock_series_app = AsyncMock() + mock_series_app.search.return_value = [ + {'title': 'Attack on Titan', 'link': '/anime/stream/attack-on-titan'} + ] + + with patch('src.server.services.setup_service.get_series_app', return_value=mock_series_app): + result = await SetupService._resolve_key_via_search("Attack on Titan") + + assert result == 'attack-on-titan' +``` ### Mocking aiohttp Sessions