2026-04-27 23:04:50 +03:00
|
|
|
import pytest
|
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>
2026-04-28 12:27:42 +03:00
|
|
|
import factory
|
|
|
|
|
from httpx import ASGITransport, AsyncClient
|
2026-04-27 23:04:50 +03:00
|
|
|
from sqlalchemy import create_engine
|
|
|
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
|
|
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>
2026-04-28 12:27:42 +03:00
|
|
|
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
|
2026-04-27 23:04:50 +03:00
|
|
|
|
|
|
|
|
|
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>
2026-04-28 12:27:42 +03:00
|
|
|
# ── Database fixtures ─────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-04-27 23:04:50 +03:00
|
|
|
@pytest.fixture(scope="session")
|
|
|
|
|
def engine():
|
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>
2026-04-28 12:27:42 +03:00
|
|
|
eng = create_engine(
|
|
|
|
|
"sqlite:///:memory:",
|
|
|
|
|
echo=False,
|
|
|
|
|
connect_args={"check_same_thread": False},
|
|
|
|
|
)
|
2026-04-27 23:04:50 +03:00
|
|
|
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()
|
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>
2026-04-28 12:27:42 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@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,
|
|
|
|
|
)
|