fix: валидация настроек обмена PositiveInt/NonNegativeInt (M-4)

OUTBOX_POLL_SECONDS и ACK_TIMEOUT_SECONDS — PositiveInt, OUTBOX_MAX_REPUBLISH —
NonNegativeInt: 0 и отрицательные значения периода и таймаута, отрицательное
число повторов отвергаются при старте. Значения по умолчанию прежние.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-04 01:53:48 +04:00
parent 3d31c1eac5
commit 7a6a7fb36b
2 changed files with 45 additions and 3 deletions

View File

@ -1,5 +1,6 @@
import os
from pydantic import NonNegativeInt, PositiveInt
from pydantic_settings import BaseSettings, SettingsConfigDict
@ -68,9 +69,12 @@ class Settings(BaseSettings):
# 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
# 0 и отрицательные значения отвергаются при старте: период 0 — горячий цикл, ack-таймаут 0 — повтор каждый проход
OUTBOX_POLL_SECONDS: PositiveInt = 5 # период опроса outbox
ACK_TIMEOUT_SECONDS: PositiveInt = 30 # ожидание ack сервиса распознавания до повторной публикации
OUTBOX_MAX_REPUBLISH: NonNegativeInt = (
3 # число повторных публикаций до перевода в dlq (0 — сразу dlq без повторов)
)
model_config = SettingsConfigDict(
extra="ignore",

View File

@ -4,6 +4,7 @@ import os
from pathlib import Path
import pytest
from pydantic import ValidationError
from src.core.config import Settings
@ -67,3 +68,40 @@ def test_empty_nats_seed_and_jwt_allowed(clean_env: pytest.MonkeyPatch) -> None:
assert s.OPENBAO.NATS.NATS_JWT == ""
assert s.NATS_ENABLED is True
assert s.OPENBAO.SMB.SMB_HOST == ""
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",
}
@pytest.mark.parametrize(
("key", "value"),
[
("OUTBOX_POLL_SECONDS", "0"),
("OUTBOX_POLL_SECONDS", "-1"),
("ACK_TIMEOUT_SECONDS", "0"),
("ACK_TIMEOUT_SECONDS", "-5"),
("OUTBOX_MAX_REPUBLISH", "-1"),
],
)
def test_exchange_settings_reject_invalid(clean_env: pytest.MonkeyPatch, key: str, value: str) -> None:
"""Период опроса и ack-таймаут — строго положительные, число повторов — неотрицательное (финальное ревью M-4)."""
for name, required in REQUIRED_ENV.items():
clean_env.setenv(name, required)
clean_env.setenv(key, value)
with pytest.raises(ValidationError, match=key):
Settings(_env_file=None) # type: ignore[call-arg]
def test_zero_republish_allowed(clean_env: pytest.MonkeyPatch) -> None:
"""OUTBOX_MAX_REPUBLISH=0 допустим: без повторов, сразу dlq после ack-таймаута."""
for name, required in REQUIRED_ENV.items():
clean_env.setenv(name, required)
clean_env.setenv("OUTBOX_MAX_REPUBLISH", "0")
assert Settings(_env_file=None).OUTBOX_MAX_REPUBLISH == 0 # type: ignore[call-arg]