- Unit tests: password hashing, notification providers, webhook field parsing - Integration tests: auth routes (register/login/confirm-email/logout), invite flow, Evotor webhooks (/user/create, /user/verify, /user/token), admin panel (access control, activate/suspend/delete/reset-password) - conftest: SQLite in-memory engine, transactional sessions, factory-boy factories (UserFactory with UserRoleEnum variants) - Fix bcrypt: replace passlib (broken on Python 3.14 + bcrypt 5.x) with direct bcrypt calls; drop passlib from requirements.txt - Fix datetime.utcnow() deprecation across routes and tests - Fix Jinja2 TemplateResponse signature (request as first positional arg) - Add coverage config to pyproject.toml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
3.1 KiB
Python
104 lines
3.1 KiB
Python
import pytest
|
|
import factory
|
|
from httpx import ASGITransport, AsyncClient
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
import web.models # noqa: F401 — ensure all tables are registered on Base.metadata
|
|
from web.auth.password import hash_password
|
|
from web.database import Base, get_db
|
|
from web.main import app
|
|
from web.models.user import User, UserRoleEnum, UserStatusEnum
|
|
|
|
|
|
# ── Database fixtures ─────────────────────────────────────────────────────────
|
|
|
|
@pytest.fixture(scope="session")
|
|
def engine():
|
|
eng = create_engine(
|
|
"sqlite:///:memory:",
|
|
echo=False,
|
|
connect_args={"check_same_thread": False},
|
|
)
|
|
Base.metadata.create_all(eng)
|
|
yield eng
|
|
Base.metadata.drop_all(eng)
|
|
|
|
|
|
@pytest.fixture
|
|
def db_session(engine):
|
|
connection = engine.connect()
|
|
transaction = connection.begin()
|
|
Session = sessionmaker(bind=connection)
|
|
session = Session()
|
|
yield session
|
|
session.close()
|
|
transaction.rollback()
|
|
connection.close()
|
|
|
|
|
|
@pytest.fixture
|
|
def override_db(db_session):
|
|
"""Override FastAPI's get_db dependency with the transactional test session."""
|
|
app.dependency_overrides[get_db] = lambda: db_session
|
|
yield db_session
|
|
app.dependency_overrides.clear()
|
|
|
|
|
|
# ── HTTP client ───────────────────────────────────────────────────────────────
|
|
|
|
@pytest.fixture
|
|
async def client(override_db):
|
|
async with AsyncClient(
|
|
transport=ASGITransport(app=app),
|
|
base_url="http://test",
|
|
) as c:
|
|
yield c
|
|
|
|
|
|
# ── Factories ─────────────────────────────────────────────────────────────────
|
|
|
|
class UserFactory(factory.alchemy.SQLAlchemyModelFactory):
|
|
class Meta:
|
|
model = User
|
|
sqlalchemy_session_persistence = "commit"
|
|
|
|
first_name = factory.Faker("first_name")
|
|
last_name = factory.Faker("last_name")
|
|
email = factory.Sequence(lambda n: f"user{n}@test.com")
|
|
phone = factory.Sequence(lambda n: f"+7900{n:07d}")
|
|
password_hash = factory.LazyFunction(lambda: hash_password("testpass123"))
|
|
status = UserStatusEnum.active
|
|
is_email_confirmed = True
|
|
role = UserRoleEnum.user
|
|
|
|
|
|
@pytest.fixture
|
|
def user_factory(db_session):
|
|
UserFactory._meta.sqlalchemy_session = db_session
|
|
return UserFactory
|
|
|
|
|
|
@pytest.fixture
|
|
def active_user(user_factory):
|
|
return user_factory.create()
|
|
|
|
|
|
@pytest.fixture
|
|
def admin_user(user_factory):
|
|
return user_factory.create(role=UserRoleEnum.admin)
|
|
|
|
|
|
@pytest.fixture
|
|
def system_user(user_factory):
|
|
return user_factory.create(role=UserRoleEnum.system)
|
|
|
|
|
|
@pytest.fixture
|
|
def pending_user(user_factory):
|
|
return user_factory.create(
|
|
status=UserStatusEnum.pending,
|
|
is_email_confirmed=False,
|
|
password_hash=None,
|
|
)
|