"""Фикстуры тестов: in-memory БД и авторизация-заглушка."""
import os
import tempfile
from collections.abc import Iterator
from typing import Any
# env-переменные — ДО импорта app.*: get_settings() кэшируется при первом вызове
_tmpdir = tempfile.mkdtemp(prefix="gntodo-att-")
os.environ.setdefault("ATTACHMENTS_PATH", _tmpdir)
os.environ.setdefault("MCP_TOKEN", "test-mcp-token")
MCP_TOKEN = os.environ["MCP_TOKEN"]
import pytest # noqa: E402
from fastapi.testclient import TestClient # noqa: E402
from sqlalchemy import create_engine # noqa: E402
from sqlalchemy.orm import Session, sessionmaker # noqa: E402
from sqlalchemy.pool import StaticPool # noqa: E402
from app import services # noqa: E402
from app.db import Base # noqa: E402
from app.dependencies import get_db, require_user # noqa: E402
from app.main import app # noqa: E402
detailing = services.detailing
TEST_DB_URL = "sqlite://"
_engine = create_engine(
TEST_DB_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(_engine)
_test_session_factory = sessionmaker(bind=_engine, expire_on_commit=False)
def _override_get_db() -> Iterator[Session]:
session = _test_session_factory()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
app.dependency_overrides[get_db] = _override_get_db
AUTH_USER: dict[str, str] = {"user_id": "1", "email": "test@example.com"}
app.dependency_overrides[require_user] = lambda: AUTH_USER
@pytest.fixture(autouse=True)
def _mock_detailing(monkeypatch: Any) -> Iterator[None]:
"""Фоновая детализация в тестах: тестовая БД и без реального вызова Ollama."""
monkeypatch.setattr(detailing, "get_session_factory", lambda: _test_session_factory)
import app.mcp_server as mcp_server
monkeypatch.setattr(mcp_server, "get_session_factory", lambda: _test_session_factory)
# Фоновый поток детализации из create_task гоняет транзакции по общему
# соединению SQLite (StaticPool) — в тестах не порождаем потоков вовсе.
monkeypatch.setattr(mcp_server, "detail_task", lambda task_id: None)
# Запрещаем сетевой вызов LLM: propose вернёт None (как при недоступной Ollama).
# Тесты при необходимости подменяют propose/generate поверх этого.
monkeypatch.setattr(
detailing.DetailingService,
"generate",
lambda self, prompt: (_ for _ in ()).throw(RuntimeError("LLM отключён в тестах")),
)
@pytest.fixture(autouse=True)
def _isolate_db() -> Iterator[None]:
"""Чистые таблицы перед каждым тестом (in-memory БД общая на процесс)."""
conn = _engine.connect()
trans = conn.begin()
for table in reversed(Base.metadata.sorted_tables):
conn.execute(table.delete())
trans.commit()
conn.close()
yield
@pytest.fixture
def client() -> Iterator[TestClient]:
# Без `with`: lifespan (старт MCP session manager) в обычных тестах не нужен,
# а менеджер сессий допускает только один запуск на инстанс.
yield TestClient(app)