"""Несекретные настройки из OpenBao: `OPENBAO__APP__<ИМЯ>` (issue #11). Приоритет: обычный env -> `OPENBAO__APP__*` -> `.env` -> значение по умолчанию. """ import os from pathlib import Path import pytest from pydantic import ValidationError from src.core.config import Settings REQUIRED_ENV = { "OPENBAO__MINIO__MINIO_URL": "localhost:9000", "OPENBAO__MINIO__MINIO_ADMIN_LOGIN": "login", "OPENBAO__MINIO__MINIO_ADMIN_PASSWORD": "password", "OPENBAO__MINIO__MINIO_BUCKET": "bucket", "OPENBAO__NATS__NATS_URL": "nats://localhost:4222", } SMB_ENV = { "OPENBAO__SMB__SMB_HOST": "smb.example", "OPENBAO__SMB__SMB_PATH": "share/dir", "OPENBAO__SMB__SMB_LOGIN": "user", "OPENBAO__SMB__SMB_PASSWORD": "secret", } FLAT_KEYS = { "DEBUG", "NATS_ENABLED", "SCAN_HOTFOLDER_MODE", "SCAN_HOTFOLDER_PATH", "SCAN_POLL_SECONDS", "PUBLISH_RETRIES", } RETRIES = 7 POLL = 2.5 PORT = 9000 @pytest.fixture def env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> pytest.MonkeyPatch: """Чистое окружение с обязательными OPENBAO__* и горячей папкой local.""" for key in list(os.environ): if key.startswith(("OPENBAO__", "SERVER__")) or key in FLAT_KEYS: monkeypatch.delenv(key) for key, value in REQUIRED_ENV.items(): monkeypatch.setenv(key, value) monkeypatch.setenv("SCAN_HOTFOLDER_PATH", str(tmp_path)) return monkeypatch def _settings(env_file: Path | None = None) -> Settings: return Settings(_env_file=env_file) # type: ignore[call-arg] def test_app_value_applied(env: pytest.MonkeyPatch) -> None: env.setenv("OPENBAO__APP__NATS_ENABLED", "false") env.setenv("OPENBAO__APP__DEBUG", "true") s = _settings() assert s.NATS_ENABLED is False assert s.DEBUG is True assert s.OPENBAO.NATS.NATS_URL == "nats://localhost:4222" def test_default_without_app(env: pytest.MonkeyPatch) -> None: assert _settings().SCAN_HOTFOLDER_MODE == "local" def test_plain_env_overrides_app(env: pytest.MonkeyPatch) -> None: env.setenv("OPENBAO__APP__NATS_ENABLED", "false") env.setenv("NATS_ENABLED", "true") assert _settings().NATS_ENABLED is True def test_app_overrides_dotenv(env: pytest.MonkeyPatch, tmp_path: Path) -> None: dotenv = tmp_path / ".env" dotenv.write_text("NATS_ENABLED=true\nPUBLISH_RETRIES=5\n", encoding="utf-8") env.setenv("OPENBAO__APP__NATS_ENABLED", "false") s = _settings(dotenv) assert s.NATS_ENABLED is False # OPENBAO__APP__ сильнее .env assert s.PUBLISH_RETRIES == 5 # noqa: PLR2004 - значение из .env def test_types_parsed_like_plain_env(env: pytest.MonkeyPatch) -> None: env.setenv("OPENBAO__APP__PUBLISH_RETRIES", str(RETRIES)) env.setenv("OPENBAO__APP__SCAN_POLL_SECONDS", str(POLL)) env.setenv("OPENBAO__APP__NATS_ENABLED", "0") s = _settings() assert s.PUBLISH_RETRIES == RETRIES assert s.SCAN_POLL_SECONDS == POLL assert s.NATS_ENABLED is False def test_invalid_value_rejected(env: pytest.MonkeyPatch) -> None: env.setenv("OPENBAO__APP__PUBLISH_RETRIES", "0") with pytest.raises(ValidationError, match="PUBLISH_RETRIES"): _settings() def test_unknown_name_is_config_error(env: pytest.MonkeyPatch) -> None: env.setenv("OPENBAO__APP__SCAN_HOTFOLDER_MOD", "smb") with pytest.raises(ValueError, match="SCAN_HOTFOLDER_MOD"): _settings() def test_openbao_group_not_settable_via_app(env: pytest.MonkeyPatch) -> None: env.setenv("OPENBAO__APP__OPENBAO__NATS__NATS_URL", "nats://x:4222") with pytest.raises(ValueError, match="OPENBAO"): _settings() def test_app_key_does_not_break_openbao_validation(env: pytest.MonkeyPatch, tmp_path: Path) -> None: """Ключ APP не попадает в OpenBAOSettings (там extra=forbid) — ни из env, ни из env-файла.""" dotenv = tmp_path / ".env" dotenv.write_text("OPENBAO__APP__NATS_ENABLED=false\n", encoding="utf-8") env.setenv("OPENBAO__APP__DEBUG", "true") s = _settings(dotenv) assert s.DEBUG is True assert not hasattr(s.OPENBAO, "APP") def test_nested_group_still_works(env: pytest.MonkeyPatch) -> None: env.setenv("OPENBAO__MINIO__MINIO_SECURE", "false") env.setenv("OPENBAO__APP__SERVER__PORT", str(PORT)) s = _settings() assert s.OPENBAO.MINIO.MINIO_SECURE is False assert s.server.PORT == PORT def test_smb_mode_from_app_applies_and_requires_smb_secrets(env: pytest.MonkeyPatch) -> None: """Режим из OPENBAO__APP__ включает проверку «при smb обязательны OPENBAO__SMB__*» и работает с ними.""" env.setenv("OPENBAO__APP__SCAN_HOTFOLDER_MODE", "smb") with pytest.raises(ValidationError, match="OPENBAO__SMB"): _settings() for key, value in SMB_ENV.items(): env.setenv(key, value) s = _settings() assert s.SCAN_HOTFOLDER_MODE == "smb" assert s.OPENBAO.SMB.SMB_PATH == "share/dir"