feat: unlock redis/postgres backend config with assembly guards

This commit is contained in:
2026-07-21 00:18:42 -04:00
parent f9677fbd8e
commit acc1bcc18a
2 changed files with 107 additions and 12 deletions
+71 -6
View File
@@ -106,10 +106,24 @@ class TestResilienceKeys:
def test_probe_ttl_derived_when_absent(self):
s = GatewaySettings.from_env("LLM", env=_env())
# 派生规则: max(2 × 最大源 timeout, cooldown)
assert s.breaker.probe_ttl_s == max(2 * 120.0, 60.0)
s2 = GatewaySettings.from_env("LLM", env=_env(**{"LLM__BREAKER__PROBE_TTL_S": "45"}))
assert s2.breaker.probe_ttl_s == 45.0
# 派生规则(M2 补第三项): max(2 × 最大源 timeout, cooldown, 最大源 timeout + 5)
assert s.breaker.probe_ttl_s == max(2 * 120.0, 60.0, 120.0 + 5)
s2 = GatewaySettings.from_env("LLM", env=_env(**{"LLM__BREAKER__PROBE_TTL_S": "300"}))
assert s2.breaker.probe_ttl_s == 300.0
def test_probe_ttl_derivation_third_term_wins(self):
# timeout=4、cooldown=2 → max(8, 2, 9) = 9;守卫 9 ≥ 4+5 恰好成立不报错
env = _env(
LLM_CIRCUIT_BREAKER_COOLDOWN="2",
**{"LLM__QWEN__1__TIMEOUT_S": "4"},
)
s = GatewaySettings.from_env("LLM", env=env)
assert s.breaker.probe_ttl_s == 9.0
def test_explicit_probe_ttl_below_guard_rejected(self):
# 守卫: probe_ttl_s ≥ max(timeout_s) + 5(CHS container 语义,M2 设计 §3)
with pytest.raises(ValueError, match="probe"):
GatewaySettings.from_env("LLM", env=_env(**{"LLM__BREAKER__PROBE_TTL_S": "45"}))
def test_selector_and_quota_full(self):
s = GatewaySettings.from_env("LLM", env=_env())
@@ -161,6 +175,57 @@ class TestAssemblyGuards:
# 有效阈值 = max(配置值 5, 并发 8 × 2) = 16(.env 注释约定入库)
assert s.breaker.fail_threshold == 16
def test_m1_only_memory_governance_backends(self):
with pytest.raises(ValueError, match="M2"):
def test_redis_governance_backend_requires_url(self):
"""M2 解禁 redis 后端: 取 redis 时 REDIS_URL 必在,缺则装配报错。"""
env = _env(
PGW_LIMITER_BACKEND="redis",
PGW_BREAKER_BACKEND="redis",
REDIS_URL="redis://:pw@10.0.0.1:6379/3",
)
s = GatewaySettings.from_env("LLM", env=env)
assert s.limiter_backend == "redis" and s.breaker_backend == "redis"
with pytest.raises(ValueError, match="REDIS_URL"):
GatewaySettings.from_env("LLM", env=_env(PGW_LIMITER_BACKEND="redis"))
def test_postgres_telemetry_requires_dsn_and_strips_driver_suffix(self):
"""M2: telemetry=postgres 需 PGW_TELEMETRY_PG_DSN;SQLAlchemy 风格驱动后缀剥离。"""
with pytest.raises(ValueError, match="PG_DSN"):
GatewaySettings.from_env("LLM", env=_env(PGW_TELEMETRY_BACKEND="postgres"))
env = _env(
PGW_TELEMETRY_BACKEND="postgres",
PGW_TELEMETRY_PG_DSN="postgresql+psycopg://u:p@h:5432/polygateway",
)
s = GatewaySettings.from_env("LLM", env=env)
assert s.telemetry_pg_dsn == "postgresql://u:p@h:5432/polygateway"
env2 = _env(
PGW_TELEMETRY_BACKEND="postgres",
PGW_TELEMETRY_PG_DSN="postgresql+asyncpg://u:p@h:5432/polygateway",
)
assert (
GatewaySettings.from_env("LLM", env=env2).telemetry_pg_dsn
== "postgresql://u:p@h:5432/polygateway"
)
def test_telemetry_backend_whitelist(self):
with pytest.raises(ValueError, match="TELEMETRY_BACKEND"):
GatewaySettings.from_env("LLM", env=_env(PGW_TELEMETRY_BACKEND="mysql"))
def test_pricing_path_optional(self):
assert GatewaySettings.from_env("LLM", env=_env()).pricing_path is None
s = GatewaySettings.from_env("LLM", env=_env(PGW_PRICING_PATH="conf/prices.json"))
assert s.pricing_path == "conf/prices.json"
def test_stall_window_must_cover_ttft(self):
"""守卫: stall_window_s ≥ 最大 ttft_timeout_s(防误判卡死,ARCH §7.3)。"""
env = _env(
**{
"LLM__QWEN__1__TTFT_TIMEOUT_S": "30",
"LLM__QWEN__1__INTER_TOKEN_TIMEOUT_S": "15",
"LLM__BACKPRESSURE__STALL_WINDOW_S": "20",
}
)
with pytest.raises(ValueError, match="stall"):
GatewaySettings.from_env("LLM", env=env)
env_ok = dict(env)
env_ok["LLM__BACKPRESSURE__STALL_WINDOW_S"] = "60"
assert GatewaySettings.from_env("LLM", env=env_ok).backpressure.stall_window_s == 60.0