test: migrate backend suites to pytest with module coverage gate

Split backend tests into unit and integration suites, add isolated fixtures and coverage enforcement, and update CI and test documentation. Cover current authentication, content, notification, payment, media, and admin flows; fix behaviors revealed by the new tests. Validate both suites and the per-module gate against local PostgreSQL.
This commit is contained in:
2026-09-26 16:50:29 +03:30
parent 3ec28638d1
commit ff99130938
76 changed files with 3271 additions and 1357 deletions

109
conftest.py Normal file
View File

@@ -0,0 +1,109 @@
"""Shared pytest fixtures and collection checks for the backend."""
import itertools
import fakeredis
import pytest
import requests
def pytest_collection_modifyitems(items):
for item in items:
parts = item.path.parts
expected = "unit" if "unit" in parts else "integration" if "integration" in parts else None
marked = [kind for kind in ("unit", "integration") if list(item.iter_markers(kind))]
if expected is None or marked != [expected]:
raise pytest.UsageError(
f"{item.nodeid}: place tests under unit/ or integration/ "
"and mark each test with exactly its directory's marker"
)
if expected == "unit" and list(item.iter_markers("django_db")):
raise pytest.UsageError(f"{item.nodeid}: unit tests cannot use the database")
@pytest.fixture(autouse=True)
def no_outbound_http(monkeypatch):
def refuse_request(*args, **kwargs):
raise AssertionError("Tests must fake outbound HTTP requests")
monkeypatch.setattr(requests.sessions.Session, "request", refuse_request)
@pytest.fixture(autouse=True)
def isolate_redis(monkeypatch):
from apps.notifications import services
from apps.users.services import auth
fake = fakeredis.FakeRedis(decode_responses=True)
otp_fake = fakeredis.FakeRedis(decode_responses=False)
monkeypatch.setattr(services, "redis_client", fake)
monkeypatch.setattr(auth, "get_redis_connection", lambda alias: otp_fake)
@pytest.fixture
def fake_redis():
return fakeredis.FakeRedis(decode_responses=False)
@pytest.fixture
def fake_notification_redis():
return fakeredis.FakeRedis(decode_responses=True)
@pytest.fixture
def user_factory(db):
from apps.users.models import User
sequence = itertools.count(1)
def create(**overrides):
index = next(sequence)
values = {
"username": f"test_user_{index}",
"mobile": f"0919000{index:04d}",
"password": "TestPass123!",
}
values.update(overrides)
return User.objects.create_user(**values)
return create
@pytest.fixture
def auth_headers():
from core.authentication import create_jwt_token
def headers(user):
return {"HTTP_AUTHORIZATION": f"Bearer {create_jwt_token(user)}"}
return headers
@pytest.fixture
def event_factory(db):
from datetime import timedelta
from django.utils import timezone
from apps.events.models import Event
sequence = itertools.count(1)
def create(**overrides):
now = timezone.now()
index = next(sequence)
values = {
"title": f"Test event {index}",
"slug": f"test-event-{index}",
"description": "Test event description",
"start_time": now + timedelta(days=2),
"end_time": now + timedelta(days=2, hours=2),
"registration_start_date": now - timedelta(days=1),
"registration_end_date": now + timedelta(days=1),
"capacity": 10,
"status": Event.StatusChoices.PUBLISHED,
}
values.update(overrides)
return Event.objects.create(**values)
return create