test: add test suite with 65 tests, 73% coverage
- 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>
This commit is contained in:
@@ -1,13 +1,25 @@
|
||||
import pytest
|
||||
import factory
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from web.database import Base
|
||||
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)
|
||||
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)
|
||||
@@ -23,3 +35,69 @@ def db_session(engine):
|
||||
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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user