-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconftest.py
More file actions
183 lines (144 loc) · 5.25 KB
/
conftest.py
File metadata and controls
183 lines (144 loc) · 5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
"""
Глобальный conftest: тестовая БД, клиент, seed-данные.
Запуск: TESTING=1 pytest tests/integration
Требуется: PostgreSQL, БД demo_saas_test (createdb demo_saas_test).
"""
import os
import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import NullPool
# Устанавливаем до импорта app/config
os.environ["TESTING"] = "1"
TEST_DB_URL = os.getenv(
"TEST_DATABASE_URL",
"postgresql+asyncpg://postgres:postgres@localhost:5432/demo_saas_test",
)
os.environ["DATABASE_URL"] = TEST_DB_URL
from core.database.base import Base
from core.utils.password import hash_password
# Регистрация моделей
import core.schemas # noqa: F401
# NullPool — каждое соединение новое, нет привязки к event loop
test_engine = create_async_engine(TEST_DB_URL, echo=False, poolclass=NullPool)
_session_factory = async_sessionmaker(
test_engine,
class_=AsyncSession,
expire_on_commit=False,
autocommit=False,
autoflush=False,
)
async def _create_tables():
async with test_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def _drop_tables():
async with test_engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
async def _truncate_tables(session: AsyncSession):
"""Очистка таблиц с учётом FK."""
try:
for table in ("refresh_tokens", "subscriptions", "users", "tenants"):
await session.execute(text(f"TRUNCATE TABLE {table} CASCADE"))
await session.commit()
except Exception:
await session.rollback()
raise
@pytest.fixture(scope="session")
def event_loop():
import asyncio
loop = asyncio.new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="session")
async def setup_test_db():
"""Создание таблиц перед всеми тестами."""
await _create_tables()
yield
await _drop_tables()
await test_engine.dispose()
@pytest.fixture
def test_async_session_fixture():
"""Фикстура: фабрика сессий для прямого доступа к БД в тестах."""
return _session_factory
@pytest.fixture
async def db_session(setup_test_db):
"""Сессия БД для каждого теста. После теста — truncate."""
async with _session_factory() as session:
yield session
async with _session_factory() as session:
await _truncate_tables(session)
@pytest.fixture
async def client(db_session):
"""Async HTTP-клиент (тот же event loop, что и БД)."""
from core.database import get_db
from main import app
async def override_get_db():
async with test_async_session() as session:
yield session
app.dependency_overrides[get_db] = override_get_db
async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test",
) as ac:
yield ac
app.dependency_overrides.pop(get_db, None)
@pytest.fixture
async def seed_tenant(db_session):
"""Создаёт tenant и возвращает его."""
from core.schemas import Tenant
tenant = Tenant(name="Test Tenant", slug="test-tenant")
db_session.add(tenant)
await db_session.commit()
await db_session.refresh(tenant)
return tenant
@pytest.fixture
async def seed_admin(seed_tenant, db_session):
"""Создаёт admin-пользователя. Возвращает (user, tenant)."""
from core.schemas import User
from core.schemas.user_schema import UserRole
user = User(
email="admin@example.com",
hashed_password=hash_password("admin123"),
tenant_id=seed_tenant.id,
role=UserRole.ADMIN,
)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
return user
@pytest.fixture
async def seed_user(seed_tenant, db_session):
"""Создаёт обычного user. Возвращает user."""
from core.schemas import User
from core.schemas.user_schema import UserRole
user = User(
email="user@example.com",
hashed_password=hash_password("user123"),
tenant_id=seed_tenant.id,
role=UserRole.USER,
)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
return user
@pytest.fixture
async def auth_headers(seed_admin, client):
"""Заголовки с токеном admin (login через API)."""
resp = await client.post(
"/api/v1/auth/login",
json={"email": "admin@example.com", "password": "admin123"},
)
assert resp.status_code == 200
token = resp.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
@pytest.fixture
async def user_auth_headers(seed_user, client):
"""Заголовки с токеном обычного user."""
resp = await client.post(
"/api/v1/auth/login",
json={"email": "user@example.com", "password": "user123"},
)
assert resp.status_code == 200
token = resp.json()["access_token"]
return {"Authorization": f"Bearer {token}"}