arm-core/api/tests/test_config_openbao_app.py
Andrey Epifancev afc457f593 feat(config): несекретные настройки из OpenBao через OPENBAO__APP__<ИМЯ> (#44)
Контур платформы передаёт в под только OPENBAO__*. Свой источник настроек
(settings_customise_sources): приоритет обычный env -> OPENBAO__APP__* -> .env -> умолчание;
неизвестное имя — ошибка при старте; ключ APP не ломает OpenBAOSettings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EAKRDNzXtRrwmWz9z6PZu3
2026-10-07 18:27:06 +04:00

133 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Несекретные настройки из OpenBao: `OPENBAO__APP__<ИМЯ>` (issue #44; без БД и сети).
Приоритет: обычный 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__DATABASE__DATABASE_URL": "postgresql+asyncpg://u:p@localhost:5432/d",
"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",
}
# Плоские настройки, которые тесты ниже задают/проверяют
POLL = 7
PORT = 9000
FLAT_KEYS = {"DEBUG", "NATS_ENABLED", "AUTH_DEV_ISSUER", "FORWARDED_ALLOW_IPS", "CORS_ORIGINS", "OUTBOX_POLL_SECONDS"}
@pytest.fixture
def env(monkeypatch: pytest.MonkeyPatch) -> pytest.MonkeyPatch:
"""Чистое окружение с обязательными OPENBAO__*; остальное из процесса убрано."""
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)
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", "true")
env.setenv("OPENBAO__APP__DEBUG", "true")
s = _settings()
assert s.NATS_ENABLED is True
assert s.DEBUG is True
# Секреты OpenBao по-прежнему читаются во вложенные группы
assert s.OPENBAO.NATS.NATS_URL == "nats://localhost:4222"
def test_default_without_app(env: pytest.MonkeyPatch) -> None:
assert _settings().NATS_ENABLED is False
def test_plain_env_overrides_app(env: pytest.MonkeyPatch) -> None:
env.setenv("OPENBAO__APP__NATS_ENABLED", "true")
env.setenv("NATS_ENABLED", "false")
assert _settings().NATS_ENABLED is False
def test_app_overrides_dotenv(env: pytest.MonkeyPatch, tmp_path: Path) -> None:
dotenv = tmp_path / ".env"
dotenv.write_text("NATS_ENABLED=false\nFORWARDED_ALLOW_IPS=1.1.1.1\n", encoding="utf-8")
env.setenv("OPENBAO__APP__NATS_ENABLED", "true")
s = _settings(dotenv)
assert s.NATS_ENABLED is True # OPENBAO__APP__ сильнее .env
assert s.FORWARDED_ALLOW_IPS == "1.1.1.1" # то, чего нет в OPENBAO__APP__, остаётся из .env
def test_types_parsed_like_plain_env(env: pytest.MonkeyPatch) -> None:
env.setenv("OPENBAO__APP__OUTBOX_POLL_SECONDS", str(POLL))
env.setenv("OPENBAO__APP__AUTH_DEV_ISSUER", "1")
env.setenv("OPENBAO__APP__FORWARDED_ALLOW_IPS", "10.0.0.1,10.0.0.2")
s = _settings()
assert s.OUTBOX_POLL_SECONDS == POLL
assert s.AUTH_DEV_ISSUER is True
assert s.FORWARDED_ALLOW_IPS == "10.0.0.1,10.0.0.2"
@pytest.mark.parametrize(
("raw", "expected"),
[
("https://a.example.ru,https://b.example.ru", ["https://a.example.ru", "https://b.example.ru"]),
('["https://a.example.ru"]', ["https://a.example.ru"]),
("", []),
],
ids=["comma", "json", "empty"],
)
def test_cors_origins_list_parsed_like_plain_env(env: pytest.MonkeyPatch, raw: str, expected: list[str]) -> None:
"""NoDecode-список разбирается валидатором так же, как из обычного env."""
env.setenv("OPENBAO__APP__CORS_ORIGINS", raw)
assert _settings().CORS_ORIGINS == expected
def test_invalid_value_rejected(env: pytest.MonkeyPatch) -> None:
env.setenv("OPENBAO__APP__OUTBOX_POLL_SECONDS", "0")
with pytest.raises(ValidationError, match="OUTBOX_POLL_SECONDS"):
_settings()
def test_unknown_name_is_config_error(env: pytest.MonkeyPatch) -> None:
"""Опечатка не игнорируется молча: ошибка при создании Settings (то есть при старте)."""
env.setenv("OPENBAO__APP__NATS_ENABLD", "true")
with pytest.raises(ValueError, match="NATS_ENABLD"):
_settings()
def test_openbao_group_not_settable_via_app(env: pytest.MonkeyPatch) -> None:
"""Секреты — только `OPENBAO__<ГРУППА>__*`; `OPENBAO__APP__OPENBAO__...` — не имя настройки."""
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=true\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