Refs #14 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b
130 lines
5.2 KiB
Python
130 lines
5.2 KiB
Python
"""Общие фикстуры тестов."""
|
||
|
||
import os
|
||
from collections.abc import Callable
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
from fixtures_helpers import (
|
||
fixtures_skip_reason,
|
||
osv_dkb_files,
|
||
osv_dkb_golden_files,
|
||
osv_fixture_files,
|
||
resolve_fixtures_dir,
|
||
statement_files,
|
||
statement_synthetic_files,
|
||
synthetic_files,
|
||
synthetic_skip_reason,
|
||
)
|
||
|
||
from src.core.config import Settings
|
||
|
||
# Переменные, которые читает Settings: убираются из окружения процесса, чтобы тест видел только свой файл
|
||
_SETTINGS_ENV_PREFIXES = (
|
||
"OPENBAO__",
|
||
"SERVER__",
|
||
"DEBUG",
|
||
"ROOT_PATH",
|
||
"VERSION",
|
||
"SERVICE_NAME",
|
||
"NATS_ENABLED",
|
||
"WORKER_CONCURRENCY",
|
||
"PUBLISH_RETRIES",
|
||
"DONE_CACHE_SIZE",
|
||
"PARSE_MAX_BYTES",
|
||
"PARSE_TIMEOUT_SECONDS",
|
||
)
|
||
|
||
# Минимальное окружение spec «Старт с минимальным окружением»: MinIO, адрес NATS и DEBUG; без seed/jwt, БД и SMB
|
||
MINIMAL_ENV = """\
|
||
DEBUG=true
|
||
OPENBAO__MINIO__MINIO_URL=localhost:9000
|
||
OPENBAO__MINIO__MINIO_ADMIN_LOGIN=minioadmin
|
||
OPENBAO__MINIO__MINIO_ADMIN_PASSWORD=minioadmin
|
||
OPENBAO__MINIO__MINIO_BUCKET=arm-ka
|
||
OPENBAO__NATS__NATS_URL=nats://localhost:4222
|
||
"""
|
||
|
||
|
||
@pytest.fixture
|
||
def minimal_settings(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Callable[[str], Settings]:
|
||
"""Фабрика Settings только из временного `.env` с `MINIMAL_ENV` и строками `extra` (окружение процесса скрыто)."""
|
||
for name in list(os.environ):
|
||
if name.upper().startswith(_SETTINGS_ENV_PREFIXES):
|
||
monkeypatch.delenv(name)
|
||
|
||
def build(extra: str = "") -> Settings:
|
||
env_file = tmp_path / ".env"
|
||
env_file.write_text(MINIMAL_ENV + extra, encoding="utf-8")
|
||
return Settings(_env_file=env_file) # type: ignore[call-arg]
|
||
|
||
return build
|
||
|
||
|
||
def statement_synthetic_skip_reason() -> str | None:
|
||
"""Причина пропуска: каталог fixtures есть, но `statements-xlsx/synthetic` отсутствует или пуст.
|
||
`None` — файлы есть или нет самих fixtures (тогда причину даёт `fixtures_skip_reason`)."""
|
||
root = resolve_fixtures_dir()
|
||
if not (root / "statements-xlsx").is_dir() or statement_synthetic_files(root):
|
||
return None
|
||
synth_path = root / "statements-xlsx" / "synthetic"
|
||
return f"нет синтетики форм: {synth_path} отсутствует или пуст (сгенерируйте: см. README fixtures)"
|
||
|
||
|
||
@pytest.fixture(scope="session")
|
||
def synthetic_dir(fixtures_dir: Path) -> Path:
|
||
"""`fixtures/osv/synthetic`; тест пропускается с причиной, если синтетики в каталоге нет."""
|
||
reason = synthetic_skip_reason()
|
||
if reason is not None:
|
||
pytest.skip(reason)
|
||
return fixtures_dir / "osv" / "synthetic"
|
||
|
||
|
||
@pytest.fixture(scope="session")
|
||
def fixtures_dir() -> Path:
|
||
"""Каталог `../fixtures`; тест пропускается с причиной, если каталога нет."""
|
||
reason = fixtures_skip_reason()
|
||
if reason is not None:
|
||
pytest.skip(reason)
|
||
return resolve_fixtures_dir()
|
||
|
||
|
||
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
|
||
"""Параметры `osv_file` (реальные ОСВ), `dkb_file` (пакет ДКБ), `dkb_golden_file` (ДКБ до порога снимка),
|
||
`synthetic_file` (синтетика ОСВ), `statement_file` (реальные формы) и `statement_synthetic_file` (синтетика форм);
|
||
без каталога — один параметр со skip."""
|
||
sources = {
|
||
"osv_file": (osv_fixture_files, fixtures_skip_reason),
|
||
"dkb_file": (osv_dkb_files, fixtures_skip_reason),
|
||
"dkb_golden_file": (osv_dkb_golden_files, fixtures_skip_reason),
|
||
"synthetic_file": (synthetic_files, lambda: fixtures_skip_reason() or synthetic_skip_reason()),
|
||
"statement_file": (statement_files, fixtures_skip_reason),
|
||
"statement_synthetic_file": (
|
||
statement_synthetic_files,
|
||
lambda: fixtures_skip_reason() or statement_synthetic_skip_reason(),
|
||
),
|
||
}
|
||
for argname, (listing, skip_reason) in sources.items():
|
||
if argname not in metafunc.fixturenames:
|
||
continue
|
||
reason = skip_reason()
|
||
if reason is not None:
|
||
metafunc.parametrize(
|
||
argname, [pytest.param(None, marks=pytest.mark.skip(reason=reason))], ids=["нет-фикстур"]
|
||
)
|
||
continue
|
||
root = resolve_fixtures_dir()
|
||
files = listing(root)
|
||
metafunc.parametrize(argname, files, ids=[path.relative_to(root).as_posix() for path in files])
|
||
|
||
|
||
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
|
||
"""Тесты с маркером `fixtures` пропускаются, если каталог фикстур недоступен."""
|
||
reason = fixtures_skip_reason()
|
||
if reason is None:
|
||
return
|
||
skip = pytest.mark.skip(reason=reason)
|
||
for item in items:
|
||
if item.get_closest_marker("fixtures"):
|
||
item.add_marker(skip)
|