feat: Settings читает ENV_FILE, поля auth/NATS/outbox, .env.example и .env.test
NATS_SEED/NATS_JWT необязательны (пусто — подключение без nkeys); в app.py разбор seed защищён от пустого значения (подключение переедет в задаче 22). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
56ea8dce0c
commit
dff1e050b9
41
api/.env.example
Normal file
41
api/.env.example
Normal file
@ -0,0 +1,41 @@
|
||||
# Пример локального окружения. Скопируйте в api/.env: cp .env.example .env
|
||||
# Порты — стандартные для docs/dev/docker-compose.yml.
|
||||
|
||||
DEBUG=true
|
||||
|
||||
# PostgreSQL (драйвер asyncpg)
|
||||
OPENBAO__DATABASE__DATABASE_URL=postgresql+asyncpg://armka:armka@localhost:5432/armka
|
||||
|
||||
# MinIO (адрес без схемы — так ждёт miniopy-async)
|
||||
OPENBAO__MINIO__MINIO_URL=localhost:9000
|
||||
OPENBAO__MINIO__MINIO_ADMIN_LOGIN=minioadmin
|
||||
OPENBAO__MINIO__MINIO_ADMIN_PASSWORD=minioadmin
|
||||
OPENBAO__MINIO__MINIO_BUCKET=arm-ka
|
||||
|
||||
# NATS без JetStream; пустые NATS_SEED/NATS_JWT — подключение без nkeys
|
||||
OPENBAO__NATS__NATS_URL=nats://localhost:4222
|
||||
OPENBAO__NATS__NATS_SEED=
|
||||
OPENBAO__NATS__NATS_JWT=
|
||||
|
||||
# SMB — заглушки (в Срезе 0 не используется)
|
||||
OPENBAO__SMB__SMB_HOST=localhost
|
||||
OPENBAO__SMB__SMB_PATH=share
|
||||
OPENBAO__SMB__SMB_LOGIN=user
|
||||
OPENBAO__SMB__SMB_PASSWORD=password
|
||||
|
||||
# Аутентификация. Dev-эмитент работает только при AUTH_DEV_ISSUER=true И DEBUG=true.
|
||||
AUTH_DEV_ISSUER=true
|
||||
# ТОЛЬКО ДЛЯ РАЗРАБОТКИ: секрет HS256 dev-эмитента, в проде не использовать
|
||||
AUTH_DEV_SECRET=dev-only-secret-do-not-use-in-production-0123456789
|
||||
# Проверка токенов РАИСА (прод): JWKS по URL либо публичный ключ PEM
|
||||
AUTH_JWKS_URL=
|
||||
AUTH_PUBLIC_KEY=
|
||||
AUTH_ISSUER=
|
||||
AUTH_AUDIENCE=
|
||||
AUTH_EMAIL_CLAIM=email
|
||||
|
||||
# Обмен с сервисами распознавания
|
||||
NATS_ENABLED=true
|
||||
OUTBOX_POLL_SECONDS=5
|
||||
ACK_TIMEOUT_SECONDS=30
|
||||
OUTBOX_MAX_REPUBLISH=3
|
||||
34
api/.env.test
Normal file
34
api/.env.test
Normal file
@ -0,0 +1,34 @@
|
||||
# Окружение тестов (pytest.ini: ENV_FILE=.env.test). Только тестовые значения, секретов здесь нет.
|
||||
# Порты — временная тестовая инфраструктура: PostgreSQL 55432, NATS 54222, MinIO 59000.
|
||||
|
||||
DEBUG=true
|
||||
|
||||
OPENBAO__DATABASE__DATABASE_URL=postgresql+asyncpg://armka:armka@localhost:55432/armka
|
||||
|
||||
OPENBAO__MINIO__MINIO_URL=localhost:59000
|
||||
OPENBAO__MINIO__MINIO_ADMIN_LOGIN=minioadmin
|
||||
OPENBAO__MINIO__MINIO_ADMIN_PASSWORD=minioadmin
|
||||
OPENBAO__MINIO__MINIO_BUCKET=arm-ka
|
||||
|
||||
OPENBAO__NATS__NATS_URL=nats://localhost:54222
|
||||
OPENBAO__NATS__NATS_SEED=
|
||||
OPENBAO__NATS__NATS_JWT=
|
||||
|
||||
OPENBAO__SMB__SMB_HOST=localhost
|
||||
OPENBAO__SMB__SMB_PATH=share
|
||||
OPENBAO__SMB__SMB_LOGIN=user
|
||||
OPENBAO__SMB__SMB_PASSWORD=password
|
||||
|
||||
AUTH_DEV_ISSUER=true
|
||||
AUTH_DEV_SECRET=test-only-secret-not-for-production-0123456789
|
||||
AUTH_JWKS_URL=
|
||||
AUTH_PUBLIC_KEY=
|
||||
AUTH_ISSUER=
|
||||
AUTH_AUDIENCE=
|
||||
AUTH_EMAIL_CLAIM=email
|
||||
|
||||
# Интеграционные тесты включают NATS сами
|
||||
NATS_ENABLED=false
|
||||
OUTBOX_POLL_SECONDS=5
|
||||
ACK_TIMEOUT_SECONDS=30
|
||||
OUTBOX_MAX_REPUBLISH=3
|
||||
@ -36,7 +36,8 @@ UP_TIME = datetime.now(UTC)
|
||||
|
||||
|
||||
# --- NATS: аутентификация по nkeys/JWT ---------------------------------------------------------
|
||||
kp = nkeys.from_seed(settings.OPENBAO.NATS.NATS_SEED.encode("utf-8"))
|
||||
# Пустой NATS_SEED — подключение без nkeys (разработка, тесты); подключение переедет в core/nats.py (задача 22)
|
||||
kp = nkeys.from_seed(settings.OPENBAO.NATS.NATS_SEED.encode("utf-8")) if settings.OPENBAO.NATS.NATS_SEED else None
|
||||
|
||||
|
||||
def jwt_cb():
|
||||
@ -44,6 +45,7 @@ def jwt_cb():
|
||||
|
||||
|
||||
def sig_cb(nonce):
|
||||
assert kp is not None
|
||||
if isinstance(nonce, str):
|
||||
nonce = nonce.encode("utf-8")
|
||||
return base64.b64encode(kp.sign(nonce))
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
import os
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class DBSettings(BaseSettings):
|
||||
@ -14,8 +16,8 @@ class MinioSettings(BaseSettings):
|
||||
|
||||
class NATSSettings(BaseSettings):
|
||||
NATS_URL: str
|
||||
NATS_SEED: str
|
||||
NATS_JWT: str
|
||||
NATS_SEED: str = "" # пусто — подключение к NATS без nkeys (локальная разработка, тесты)
|
||||
NATS_JWT: str = ""
|
||||
|
||||
|
||||
class SMBSettings(BaseSettings):
|
||||
@ -38,17 +40,40 @@ class ServerSettings(BaseSettings):
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Настройки приложения.
|
||||
|
||||
Файл окружения выбирается переменной ``ENV_FILE`` (по умолчанию ``.env``; значение читается при импорте модуля,
|
||||
относительный путь считается от текущего каталога — ``api/``). Тесты используют ``.env.test``.
|
||||
"""
|
||||
|
||||
OPENBAO: OpenBAOSettings
|
||||
DEBUG: bool = False
|
||||
server: ServerSettings = ServerSettings()
|
||||
ROOT_PATH: str = "/"
|
||||
VERSION: str = "1.0.0"
|
||||
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
env_file = ".env"
|
||||
env_nested_delimiter = "__"
|
||||
nested_model_default_partial_update = False
|
||||
# Аутентификация: dev-эмитент (только для разработки; роутер подключается при AUTH_DEV_ISSUER и DEBUG одновременно)
|
||||
AUTH_DEV_ISSUER: bool = False
|
||||
AUTH_DEV_SECRET: str = ""
|
||||
# Проверка токенов РАИСА: JWKS по URL либо статический публичный ключ (PEM)
|
||||
AUTH_JWKS_URL: str = ""
|
||||
AUTH_PUBLIC_KEY: str = ""
|
||||
AUTH_ISSUER: str = ""
|
||||
AUTH_AUDIENCE: str = ""
|
||||
AUTH_EMAIL_CLAIM: str = "email" # claim токена с email пользователя (сопоставляется с app_user.email)
|
||||
|
||||
# NATS и outbox/inbox обмена recognition_job
|
||||
NATS_ENABLED: bool = False
|
||||
OUTBOX_POLL_SECONDS: int = 5 # период опроса outbox
|
||||
ACK_TIMEOUT_SECONDS: int = 30 # ожидание ack сервиса распознавания до повторной публикации
|
||||
OUTBOX_MAX_REPUBLISH: int = 3 # число повторных публикаций до перевода в dlq
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
extra="ignore",
|
||||
env_file=os.environ.get("ENV_FILE", ".env"),
|
||||
env_nested_delimiter="__",
|
||||
nested_model_default_partial_update=False,
|
||||
)
|
||||
|
||||
|
||||
def get_settings() -> Settings:
|
||||
|
||||
43
api/tests/test_config.py
Normal file
43
api/tests/test_config.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""Настройки: чтение ENV_FILE и значения по умолчанию (без БД и сети)."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from src.core.config import Settings
|
||||
|
||||
API_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clean_env(monkeypatch: pytest.MonkeyPatch) -> pytest.MonkeyPatch:
|
||||
"""Убирает из окружения процесса переменные, которые перебили бы значения из env-файла."""
|
||||
for key in list(os.environ):
|
||||
if key.startswith(("OPENBAO__", "AUTH_", "OUTBOX_", "ACK_")) or key in {"NATS_ENABLED", "DEBUG"}:
|
||||
monkeypatch.delenv(key)
|
||||
return monkeypatch
|
||||
|
||||
|
||||
def test_env_test_is_read(clean_env: pytest.MonkeyPatch) -> None:
|
||||
""".env.test читается: порты временной тестовой инфраструктуры."""
|
||||
s = Settings(_env_file=API_DIR / ".env.test") # type: ignore[call-arg]
|
||||
assert s.OPENBAO.DATABASE.DATABASE_URL.endswith("localhost:55432/armka")
|
||||
assert s.OPENBAO.NATS.NATS_URL == "nats://localhost:54222"
|
||||
assert s.AUTH_DEV_ISSUER is True
|
||||
assert s.NATS_ENABLED is False
|
||||
|
||||
|
||||
def test_defaults(clean_env: pytest.MonkeyPatch) -> None:
|
||||
"""Значения по умолчанию: claim email и параметры outbox."""
|
||||
s = Settings(_env_file=API_DIR / ".env.test") # type: ignore[call-arg]
|
||||
assert s.AUTH_EMAIL_CLAIM == "email"
|
||||
assert (s.OUTBOX_POLL_SECONDS, s.ACK_TIMEOUT_SECONDS, s.OUTBOX_MAX_REPUBLISH) == (5, 30, 3)
|
||||
|
||||
|
||||
def test_empty_nats_seed_and_jwt_allowed(clean_env: pytest.MonkeyPatch) -> None:
|
||||
"""Пустые NATS_SEED/NATS_JWT допустимы (подключение без nkeys)."""
|
||||
s = Settings(_env_file=API_DIR / ".env.example") # type: ignore[call-arg]
|
||||
assert s.OPENBAO.NATS.NATS_SEED == ""
|
||||
assert s.OPENBAO.NATS.NATS_JWT == ""
|
||||
assert s.NATS_ENABLED is True
|
||||
Loading…
x
Reference in New Issue
Block a user