- 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
147 lines
4.3 KiB
Markdown
147 lines
4.3 KiB
Markdown
# Testing Documentation
|
|
|
|
## Document Purpose
|
|
|
|
This document describes the testing strategy, guidelines, and practices for the Aniworld project.
|
|
|
|
### What This Document Contains
|
|
|
|
- **Testing Strategy**: Overall approach to quality assurance
|
|
- **Test Categories**: Unit, integration, API, performance, security tests
|
|
- **Test Structure**: Organization of test files and directories
|
|
- **Writing Tests**: Guidelines for writing effective tests
|
|
- **Fixtures and Mocking**: Shared test utilities and mock patterns
|
|
- **Running Tests**: Commands and configurations
|
|
- **Coverage Requirements**: Minimum coverage thresholds
|
|
- **CI/CD Integration**: How tests run in automation
|
|
- **Test Data Management**: Managing test fixtures and data
|
|
- **Best Practices**: Do's and don'ts for testing
|
|
|
|
### What This Document Does NOT Contain
|
|
|
|
- Production deployment (see [DEPLOYMENT.md](DEPLOYMENT.md))
|
|
- Security audit procedures (see [SECURITY.md](SECURITY.md))
|
|
- Bug tracking and issue management
|
|
- Performance benchmarking results
|
|
|
|
### Target Audience
|
|
|
|
- Developers writing tests
|
|
- QA Engineers
|
|
- CI/CD Engineers
|
|
- Code reviewers
|
|
|
|
---
|
|
|
|
## Sections to Document
|
|
|
|
1. Testing Philosophy
|
|
- Test pyramid approach
|
|
- Quality gates
|
|
2. Test Categories
|
|
- Unit Tests (`tests/unit/`)
|
|
- Integration Tests (`tests/integration/`)
|
|
- API Tests (`tests/api/`)
|
|
- Frontend Tests (`tests/frontend/`)
|
|
- Performance Tests (`tests/performance/`)
|
|
- Security Tests (`tests/security/`)
|
|
3. Test Structure and Naming
|
|
- File naming conventions
|
|
- Test function naming
|
|
- Test class organization
|
|
4. Running Tests
|
|
- pytest commands
|
|
- Running specific tests
|
|
- Verbose output
|
|
- Coverage reports
|
|
5. Fixtures and Conftest
|
|
- Shared fixtures
|
|
- Database fixtures
|
|
- Mock services
|
|
6. Mocking Guidelines
|
|
- What to mock
|
|
- Mock patterns
|
|
- External service mocks
|
|
|
|
### Mocking the Download Queue
|
|
|
|
Use `MockQueueRepository` for testing download queue functionality:
|
|
|
|
```python
|
|
from src.server.models.download import DownloadItem, EpisodeIdentifier
|
|
|
|
class MockQueueRepository:
|
|
def __init__(self):
|
|
self._items: Dict[str, DownloadItem] = {}
|
|
```
|
|
|
|
### 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
|
|
|
|
When testing code that uses `aiohttp.ClientSession`:
|
|
|
|
```python
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
from aiohttp import ClientSession
|
|
|
|
# Mock aiohttp session for testing
|
|
class MockAiohttpSession:
|
|
def __init__(self):
|
|
self.closed = False
|
|
|
|
async def close(self):
|
|
self.closed = True
|
|
|
|
def get(self, url, **kwargs):
|
|
mock_response = AsyncMock()
|
|
mock_response.status = 200
|
|
mock_response.json = AsyncMock(return_value={"data": "test"})
|
|
mock_response.__aenter__ = AsyncMock(return_value=mock_response)
|
|
mock_response.__aexit__ = AsyncMock(return_value=None)
|
|
return mock_response
|
|
|
|
# Use in fixture
|
|
@pytest.fixture
|
|
async def mock_tmdb_session():
|
|
session = MockAiohttpSession()
|
|
yield session
|
|
# Cleanup verification
|
|
assert session.closed, "Session was not closed"
|
|
```
|
|
|
|
**Key points:**
|
|
- Always verify `session.closed` is `True` after context manager exits
|
|
- Mock `__aenter__` and `__aexit__` for response context managers
|
|
- Set `closed = False` on mock session for unclosed warning tests
|
|
|
|
7. Coverage Requirements
|
|
8. CI/CD Integration
|
|
9. Writing Good Tests
|
|
- Arrange-Act-Assert pattern
|
|
- Test isolation
|
|
- Edge cases
|
|
10. Common Pitfalls to Avoid
|