"""Общие фикстуры тестов.""" import os from collections.abc import Callable from pathlib import Path import pytest from fixtures_helpers import fixtures_skip_reason, osv_fixture_files, resolve_fixtures_dir, synthetic_files 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", ) # Минимальное окружение 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 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` (реальные ОСВ) и `synthetic_file` (синтетика); без каталога — один параметр со skip.""" sources = {"osv_file": osv_fixture_files, "synthetic_file": synthetic_files} for argname, listing in sources.items(): if argname not in metafunc.fixturenames: continue reason = fixtures_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)