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
This commit is contained in:
@@ -81,6 +81,7 @@ src/server/
|
|||||||
| +-- websocket_service.py# WebSocket broadcasting
|
| +-- websocket_service.py# WebSocket broadcasting
|
||||||
| +-- queue_repository.py # Database persistence
|
| +-- queue_repository.py # Database persistence
|
||||||
| +-- nfo_service.py # NFO metadata management
|
| +-- nfo_service.py # NFO metadata management
|
||||||
|
| +-- setup_service.py # Series key resolution from folder names
|
||||||
| +-- folder_scan_service.py # Daily folder maintenance scan
|
| +-- folder_scan_service.py # Daily folder maintenance scan
|
||||||
+-- models/ # Pydantic models
|
+-- models/ # Pydantic models
|
||||||
| +-- auth.py # Auth request/response models
|
| +-- auth.py # Auth request/response models
|
||||||
|
|||||||
@@ -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
|
## [1.3.1] - 2026-02-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -73,40 +73,31 @@ from src.server.models.download import DownloadItem, EpisodeIdentifier
|
|||||||
class MockQueueRepository:
|
class MockQueueRepository:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._items: Dict[str, DownloadItem] = {}
|
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:**
|
### Testing SetupService
|
||||||
- The mock uses in-memory storage, no database required
|
|
||||||
- All async methods are implemented (even if just pass-through)
|
SetupService handles series key resolution from folder names during library setup. Test file: `tests/unit/test_setup_service.py`.
|
||||||
- `save_item` uses `item.id` as key (must be set before calling)
|
|
||||||
- Suitable for unit tests only (no persistence)
|
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
|
### Mocking aiohttp Sessions
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user