Skip to content

Testing

Ingwald edited this page Aug 15, 2026 · 2 revisions

Testing

Structure

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

Coverage Gate

Minimum: 85%. Baseline: ~95%.

CI fails if coverage drops below 85%. Run locally:

python -m pytest --cov=app --cov-report=term-missing

PostgreSQL Integration Tests

The 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-redis

PowerShell:

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-redis

To 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

Core Fixtures (conftest.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

Mocking HTTP Calls

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))

Property-Based Tests

hypothesis is used for property-based testing of text normalization, query parsing, and edge case inputs. Defined alongside regular unit tests.

Test Configuration

pyproject.toml [tool.pytest.ini_options]:

  • asyncio_mode = "auto" β€” all async def test_* run under pytest-asyncio automatically
  • Coverage source: app/
  • Fail-under: 85

Clone this wiki locally