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:
95
tests/test_routes_invite.py
Normal file
95
tests/test_routes_invite.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Integration tests for the Evotor invite flow (/invite)."""
|
||||
import secrets
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from web.models.user import User, UserStatusEnum
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invite_get_valid_token(client, override_db, user_factory):
|
||||
token = secrets.token_urlsafe(32)
|
||||
user = user_factory.create(
|
||||
invite_token=token,
|
||||
invite_expires=datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(hours=48),
|
||||
password_hash=None,
|
||||
status=UserStatusEnum.pending,
|
||||
is_email_confirmed=False,
|
||||
)
|
||||
resp = await client.get(f"/invite?token={token}")
|
||||
assert resp.status_code == 200
|
||||
assert "Завершение регистрации" in resp.text or "ЭВОСИНК" in resp.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invite_get_expired_token(client, user_factory):
|
||||
token = secrets.token_urlsafe(32)
|
||||
user_factory.create(
|
||||
invite_token=token,
|
||||
invite_expires=datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=1),
|
||||
password_hash=None,
|
||||
status=UserStatusEnum.pending,
|
||||
)
|
||||
resp = await client.get(f"/invite?token={token}")
|
||||
assert resp.status_code == 200
|
||||
assert "недействительна" in resp.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invite_get_bogus_token(client):
|
||||
resp = await client.get("/invite?token=notexist")
|
||||
assert resp.status_code == 200
|
||||
assert "недействительна" in resp.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invite_post_activates_user(client, override_db, user_factory):
|
||||
token = secrets.token_urlsafe(32)
|
||||
user = user_factory.create(
|
||||
email="invite@test.com",
|
||||
phone="+79001119999",
|
||||
invite_token=token,
|
||||
invite_expires=datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(hours=48),
|
||||
password_hash=None,
|
||||
status=UserStatusEnum.pending,
|
||||
is_email_confirmed=False,
|
||||
)
|
||||
resp = await client.post(f"/invite?token={token}", data={
|
||||
"first_name": "Петр",
|
||||
"last_name": "Петров",
|
||||
"email": "invite@test.com",
|
||||
"phone": "+79001119999",
|
||||
"password": "newpassword1",
|
||||
"password_confirm": "newpassword1",
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
assert "активирован" in resp.text.lower()
|
||||
|
||||
override_db.refresh(user)
|
||||
assert user.status == UserStatusEnum.active
|
||||
assert user.is_email_confirmed is True
|
||||
assert user.password_hash is not None
|
||||
assert user.invite_token is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invite_post_password_mismatch(client, user_factory):
|
||||
token = secrets.token_urlsafe(32)
|
||||
user_factory.create(
|
||||
invite_token=token,
|
||||
invite_expires=datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(hours=48),
|
||||
password_hash=None,
|
||||
status=UserStatusEnum.pending,
|
||||
)
|
||||
resp = await client.post(f"/invite?token={token}", data={
|
||||
"first_name": "А",
|
||||
"last_name": "Б",
|
||||
"email": "x@test.com",
|
||||
"phone": "+79001112233",
|
||||
"password": "password123",
|
||||
"password_confirm": "different",
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
assert "не совпадают" in resp.text
|
||||
Reference in New Issue
Block a user