138 lines
4.4 KiB
Python
138 lines
4.4 KiB
Python
import asyncio
|
||
from contextlib import contextmanager
|
||
import os
|
||
|
||
import pytest
|
||
from fastapi.testclient import TestClient
|
||
from sqlalchemy import text
|
||
|
||
|
||
from src.db.base import SessionLocal
|
||
from src.db.session import get_db
|
||
from src.main import app
|
||
|
||
|
||
|
||
@pytest.fixture
|
||
def client():
|
||
db_ref = SessionLocal()
|
||
loop_ref = None
|
||
|
||
async def _get_db():
|
||
nonlocal loop_ref
|
||
loop_ref = asyncio.get_running_loop()
|
||
try:
|
||
yield db_ref
|
||
await db_ref.flush()
|
||
except:
|
||
await db_ref.rollback()
|
||
raise
|
||
|
||
app.dependency_overrides[get_db] = _get_db
|
||
with TestClient(app) as test_client:
|
||
try:
|
||
test_client.db_session = db_ref
|
||
yield test_client
|
||
finally:
|
||
if db_ref is not None and loop_ref:
|
||
async def cleanup():
|
||
await db_ref.rollback()
|
||
await db_ref.close()
|
||
|
||
future = asyncio.run_coroutine_threadsafe(cleanup(), loop_ref)
|
||
future.result()
|
||
app.dependency_overrides.clear()
|
||
|
||
|
||
@pytest.fixture
|
||
def admin_password() -> str:
|
||
# Пароль админа берём из окружения, чтобы не хардкодить локальные отличия.
|
||
return os.getenv("OPENBAO__TEST_ADMIN_PASSWORD", "admin123")
|
||
|
||
|
||
@pytest.fixture
|
||
def isp_password() -> str:
|
||
# Пароль админа берём из окружения, чтобы не хардкодить локальные отличия.
|
||
return os.getenv("OPENBAO__TEST_EXECUTOR_PASSWORD", "admin123")
|
||
|
||
|
||
@pytest.fixture
|
||
def admin_tokens(client, admin_password: str):
|
||
response = client.post(
|
||
"/api/v1/auth/login",
|
||
json={"username": "admin", "password": admin_password},
|
||
)
|
||
assert response.status_code == 200
|
||
return response.json()
|
||
|
||
|
||
@pytest.fixture
|
||
def auth_headers():
|
||
def _build(tokens: dict) -> dict:
|
||
return {"Authorization": f"Bearer {tokens['access_token']}"}
|
||
|
||
return _build
|
||
|
||
@pytest.fixture
|
||
def isp_tokens(client, isp_password: str):
|
||
response = client.post(
|
||
"/api/v1/auth/login",
|
||
json={"username": "isp1", "password": isp_password},
|
||
)
|
||
assert response.status_code == 200
|
||
return response.json()
|
||
|
||
|
||
SVOD2_YEAR = 2031
|
||
SVOD2_SEED_PATH = os.path.join(os.path.dirname(__file__), "..", "sqls", "svod2_seed.sql")
|
||
|
||
|
||
def legacy_full(client, sheet: str, headers: dict, org_ids=None) -> list[dict]:
|
||
"""Прежний ответ свода 2 без параметров нового режима (оракульная база)."""
|
||
url = f"/api/v1/svod/2/{sheet}/{SVOD2_YEAR}"
|
||
if org_ids is not None:
|
||
url += f"?org_ids={org_ids}"
|
||
response = client.get(url, headers=headers)
|
||
assert response.status_code == 200, response.text
|
||
return response.json()["result"]
|
||
|
||
|
||
@pytest.fixture
|
||
def svod2_sql(client):
|
||
"""Выполняет text(sql) на сессии клиента (тот же цикл событий, что у запросов)."""
|
||
|
||
def _run(sql: str, params: dict | None = None):
|
||
async def _exec():
|
||
result = await client.db_session.execute(text(sql), params or {})
|
||
return list(result.fetchall()) if result.returns_rows else []
|
||
|
||
return client.portal.call(_exec)
|
||
|
||
return _run
|
||
|
||
|
||
@pytest.fixture
|
||
def svod2_data(client, svod2_sql) -> dict:
|
||
"""Засевает данные свода 2 (откатываются вместе с сессией клиента)."""
|
||
with open(SVOD2_SEED_PATH, "r", encoding="utf-8") as f:
|
||
script = f.read()
|
||
for statement in script.split(";"):
|
||
statement = statement.strip()
|
||
if statement:
|
||
svod2_sql(statement)
|
||
# id строки contract_detail: contract_detail.line_id = 910002, технический id,
|
||
# который попадает в шапку строки, — expense_item_id этой строки
|
||
cd_id = svod2_sql("SELECT expense_item_id FROM v3.budget_line WHERE id = 910002")[0][0]
|
||
yield {
|
||
"year": SVOD2_YEAR,
|
||
"org_a": 910001,
|
||
"org_b": 910002,
|
||
"org_b_twin": 910003,
|
||
"org_b_case": 910004,
|
||
"cd_id": cd_id,
|
||
"ckk_only_value": "777.25",
|
||
}
|
||
# откат, пока portal открыт: иначе при отсутствии запросов к API транзакция засева
|
||
# остаётся висеть и блокирует следующий засев
|
||
client.portal.call(client.db_session.rollback)
|