"""Общие фикстуры тестов.""" 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, 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 @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` (синтетика); без каталога — один параметр со 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()), } 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)