Repository navigation
Testing
Ingwald edited this page Aug 15, 2026
·
2 revisions
tests/
βββ conftest.py β shared fixtures (AsyncFakeBot, fake_message, to_async, FakeAsyncClient, ...)
βββ test_bot_actions.py
βββ test_bot_callbacks_coverage.py
βββ test_bot_handlers_coverage.py
βββ test_callback_router.py
βββ test_deezer_service_more_coverage.py
βββ test_lyrics_service.py
βββ test_main_runtime.py
βββ test_monitoring.py
βββ test_spotify_auth_client_more_coverage.py
βββ test_track_formatter.py
βββ [9 PostgreSQL integration tests]
test_users_pg.py
test_tracks_pg.py
test_searches_pg.py
test_favorites_pg.py
test_errors_pg.py
test_health_pg.py
test_maintenance_pg.py
test_search_cache_pg.py
test_spotify_pg.py
test_admin_audit_pg.py
Minimum: 85%. Baseline: ~95%.
CI fails if coverage drops below 85%. Run locally:
python -m pytest --cov=app --cov-report=term-missingThe test_*_pg.py files hit a real PostgreSQL instance. They need DATABASE_URL set.
Bash:
docker compose up -d --wait test-postgres test-redis
DATABASE_URL=postgresql://testuser:testpass@localhost:5433/testdb \
REDIS_URL=redis://localhost:6380 \
python -m pytest
docker compose stop test-postgres test-redisPowerShell:
docker compose up -d --wait test-postgres test-redis
$env:DATABASE_URL = "postgresql://testuser:testpass@localhost:5433/testdb"
$env:REDIS_URL = "redis://localhost:6380"
python -m pytest
docker compose stop test-postgres test-redisTo skip PG tests (unit tests only):
python -m pytest --ignore=tests/test_users_pg.py --ignore=tests/test_tracks_pg.py \
--ignore=tests/test_searches_pg.py --ignore=tests/test_favorites_pg.py \
--ignore=tests/test_errors_pg.py --ignore=tests/test_health_pg.py \
--ignore=tests/test_maintenance_pg.py --ignore=tests/test_search_cache_pg.py \
--ignore=tests/test_spotify_pg.py --ignore=tests/test_admin_audit_pg.py| Fixture | Description |
|---|---|
AsyncFakeBot |
Minimal aiogram Bot stand-in. Records send_message, answer_callback_query calls. |
fake_message(bot, user_id) |
Minimal Message stand-in for handler tests |
fake_call(bot, user_id, data) |
Minimal CallbackQuery stand-in for callback tests |
to_async(fn) |
Wraps a sync callable into an async one for monkeypatching awaited calls |
FakeAsyncClient(response, responses, exc) |
httpx.AsyncClient stand-in for HTTP mocking |
reset_circuit_breakers() |
Clears all per-service breaker state between tests |
clear_rate_limits() |
Resets in-memory rate limit counters |
clear_search_contexts() |
Clears in-memory search pagination state |
Services use get_http_client() (from app/utils/http_client.py). Patch at the module level:
import app.services.deezer_service as deezer_service
monkeypatch.setattr(deezer_service, "get_http_client", lambda: FakeAsyncClient(response=mock_resp))For multi-request tests (e.g. get_artist_top_tracks makes two sequential calls):
clients = iter([FakeAsyncClient(responses=[r1, r2]), FakeAsyncClient(exc=ConnectError(...))])
monkeypatch.setattr(deezer_service, "get_http_client", lambda: next(clients))hypothesis is used for property-based testing of text normalization, query parsing, and edge case inputs. Defined alongside regular unit tests.
pyproject.toml [tool.pytest.ini_options]:
-
asyncio_mode = "auto"β allasync def test_*run under pytest-asyncio automatically - Coverage source:
app/ - Fail-under: 85