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:
109
conftest.py
Normal file
109
conftest.py
Normal 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
|
||||
Reference in New Issue
Block a user