Files
fin-tracker/backend/tests/conftest.py
T
Dmitry d2df86ce33 feat(moex): история бумаг с листинга и цены индексов для бенчмарков
Окно бэкфилла начинается с history_from доски, а не с первой покупки: график цены показывает историю бумаги целиком. Для активных бенчмарков source=moex синк создаёт instrument и качает индекс с его доски (IMOEX и RGBITR на SNDX, MCFTR на RTSI). Миграция засевает IMOEX, MCFTR и RGBITR; тесты чистят таблицы перед каждым тестом, чтобы сид не попадал в первый из них.
2026-09-20 10:46:24 +03:00

101 lines
3.3 KiB
Python

"""Test harness: a throwaway Postgres (pytest-postgresql + pg_ctl on PATH), migrated with
Alembic once per session, tables truncated after every test."""
from __future__ import annotations
import os
from collections.abc import AsyncIterator, Iterator
from pathlib import Path
import pytest
from httpx import ASGITransport, AsyncClient
from pytest_postgresql import factories
from pytest_postgresql.janitor import DatabaseJanitor
from sqlalchemy import text
BACKEND_DIR = Path(__file__).resolve().parent.parent
postgresql_proc = factories.postgresql_proc(port=None, unixsocketdir="/tmp")
@pytest.fixture(scope="session")
def database_url(postgresql_proc) -> Iterator[str]:
p = postgresql_proc
with DatabaseJanitor(
user=p.user,
host=p.host,
port=p.port,
dbname="fintracker_test",
password=p.password,
):
pw = f":{p.password}" if p.password else ""
url = f"postgresql+asyncpg://{p.user}{pw}@{p.host}:{p.port}/fintracker_test"
os.environ["DATABASE_URL"] = url
os.environ["JWT_SECRET"] = "test-secret-not-for-production-0123456789"
from fintracker.config import get_settings
get_settings.cache_clear()
yield url
@pytest.fixture(scope="session")
def migrated(database_url: str) -> str:
from alembic import command
from alembic.config import Config
cfg = Config(str(BACKEND_DIR / "alembic.ini"))
cfg.set_main_option("script_location", str(BACKEND_DIR / "alembic"))
command.upgrade(cfg, "head")
return database_url
@pytest.fixture
async def app(migrated: str):
from fintracker.api import app as app_module
from fintracker.api.routers import auth as auth_router
from fintracker.db import reset_engine
auth_router._login_limiter = None # fresh rate limiter per test
# rows a migration seeds (the default benchmarks) are in the first test's database only,
# since the tables are emptied after each test — start every test from the same blank one
await _truncate_all()
application = app_module.create_app()
yield application
await _truncate_all()
await reset_engine()
async def _truncate_all() -> None:
from fintracker.db import get_engine
from fintracker.db.base import Base
tables = ", ".join(f'"{t.name}"' for t in Base.metadata.sorted_tables)
async with get_engine().begin() as conn:
await conn.execute(text(f"TRUNCATE {tables} RESTART IDENTITY CASCADE"))
@pytest.fixture
async def client(app) -> AsyncIterator[AsyncClient]:
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c:
yield c
@pytest.fixture
async def user(app) -> dict[str, str]:
from fintracker.api.security import hash_password
from fintracker.db import get_sessionmaker
from fintracker.models import AppUser
creds = {"email": "ada@example.com", "password": "correct horse battery staple"}
async with get_sessionmaker()() as session:
session.add(AppUser(email=creds["email"], password_hash=hash_password(creds["password"])))
await session.commit()
return creds
@pytest.fixture
async def auth_headers(client: AsyncClient, user: dict[str, str]) -> dict[str, str]:
r = await client.post("/api/v1/auth/login", json=user)
assert r.status_code == 200, r.text
return {"Authorization": f"Bearer {r.json()['access_token']}"}