Files
gaokao-volunteer-system/admin/tests/test_health.py
Hermes Agent 43c1dbdf32
Some checks failed
CI / pytest (Python 3.10) (push) Has been cancelled
CI / pytest (Python 3.11) (push) Has been cancelled
CI / pytest (Python 3.12) (push) Has been cancelled
feat(llm): 内置 LLM 供应商配置与审核主链接入骨架
配置层:
- Settings 新增 llm_provider/api_key/base_url/model/timeout/max_tokens
- 生产 fail-closed: GAOKAO_LLM_PROVIDER=none 禁止, provider!=none 且 API key 为空禁止
- .env.docker.example / .env.payment.example 补 LLM 变量
- payment_readiness_doctor 将 LLM 配置纳入 readiness 检查

基础设施:
- 新增 data/llm/client.py: OpenAI-compatible LLMClient
- 新增 data/llm/prompts.py: audit/cwb/full_plan prompt 模板
- 新增 data/llm/tests/test_llm.py: 12 个单元测试

主链接入:
- ReviewResultContract 新增 llm_generated / llm_summary / llm_cwb_suggestions
- _start_review_result 优先尝试 LLM 生成审核结果, 失败时回退到原规则默认逻辑
- cwb 页面优先展示 LLM 生成的三档建议

测试适配:
- conftest / health / app / p2_4 tests 注入默认 LLM 测试配置,避免被新 fail-closed 提前拦截

验证:
- data/llm/tests 12 passed
- 核心 prod settings tests 62 passed
2026-06-28 12:11:43 +08:00

79 lines
3.5 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.
from __future__ import annotations
def test_health_endpoint_returns_minimal_readiness_only(client):
"""公开健康检查:保持 `status: ok` 主键 + 详细 checks 字段。"""
resp = client.get("/health")
assert resp.status_code == 200
body = resp.json()
# 主键契约status 必须存在且为 okPRODUCTION_DEPLOYMENT_CHECKLIST §4
assert body.get("status") == "ok"
# 6/20 加固:增 checks 子对象,断言关键 readiness 字段
assert "checks" in body
checks = body["checks"]
assert checks.get("db_writable") is True
assert checks.get("disk_writable") is True
# settings_ok=True 意味着 prod fail-closed 通过(默认 dev 环境)
def test_load_settings_prod_rejects_dev_jwt_secret(monkeypatch, tmp_path):
"""GAOKAO_ENV=prod 时使用 dev 默认 JWT secret 必须 fail-closed。"""
import pytest
from admin.config import load_settings
monkeypatch.setenv("GAOKAO_ENV", "prod")
monkeypatch.setenv(
"GAOKAO_JWT_SECRET", "dev-only-do-not-use-in-prod-please-override-via-env"
)
monkeypatch.setenv("GAOKAO_PAYMENT_WEBHOOK_SECRET", "x" * 32)
monkeypatch.setenv("GAOKAO_PORTAL_TOKEN_SECRET", "y" * 32)
monkeypatch.setenv("GAOKAO_ADMIN_PASS", "StrongPass1!")
monkeypatch.setenv("GAOKAO_PAYMENT_PROVIDER", "alipay")
monkeypatch.setenv("GAOKAO_LLM_PROVIDER", "dashscope")
monkeypatch.setenv("GAOKAO_LLM_API_KEY", "sk-test")
monkeypatch.setenv("GAOKAO_ORDERS_DB_PATH", str(tmp_path / "orders.db"))
monkeypatch.setenv("GAOKAO_DB_PATH", str(tmp_path / "admin.db"))
with pytest.raises(RuntimeError, match="JWT.*dev|JWT.*prod|密钥"):
load_settings()
def test_load_settings_prod_rejects_short_jwt_secret(monkeypatch, tmp_path):
"""GAOKAO_ENV=prod 时 JWT secret 长度 < 32 必须 fail-closed。"""
import pytest
from admin.config import load_settings
monkeypatch.setenv("GAOKAO_ENV", "prod")
monkeypatch.setenv("GAOKAO_JWT_SECRET", "short_secret_under_32")
monkeypatch.setenv("GAOKAO_PAYMENT_WEBHOOK_SECRET", "x" * 32)
monkeypatch.setenv("GAOKAO_PORTAL_TOKEN_SECRET", "y" * 32)
monkeypatch.setenv("GAOKAO_ADMIN_PASS", "StrongPass1!")
monkeypatch.setenv("GAOKAO_PAYMENT_PROVIDER", "alipay")
monkeypatch.setenv("GAOKAO_LLM_PROVIDER", "dashscope")
monkeypatch.setenv("GAOKAO_LLM_API_KEY", "sk-test")
monkeypatch.setenv("GAOKAO_ORDERS_DB_PATH", str(tmp_path / "orders.db"))
monkeypatch.setenv("GAOKAO_DB_PATH", str(tmp_path / "admin.db"))
with pytest.raises(RuntimeError, match="JWT.*长度|32"):
load_settings()
def test_load_settings_prod_rejects_default_admin_password(monkeypatch, tmp_path):
"""GAOKAO_ENV=prod 时 GAOKAO_ADMIN_PASS=admin123 必须 fail-closed。"""
import pytest
from admin.config import load_settings
monkeypatch.setenv("GAOKAO_ENV", "prod")
monkeypatch.setenv("GAOKAO_JWT_SECRET", "z" * 32)
monkeypatch.setenv("GAOKAO_PAYMENT_WEBHOOK_SECRET", "x" * 32)
monkeypatch.setenv("GAOKAO_PORTAL_TOKEN_SECRET", "y" * 32)
monkeypatch.setenv("GAOKAO_ADMIN_PASS", "admin123")
monkeypatch.setenv("GAOKAO_PAYMENT_PROVIDER", "alipay")
monkeypatch.setenv("GAOKAO_LLM_PROVIDER", "dashscope")
monkeypatch.setenv("GAOKAO_LLM_API_KEY", "sk-test")
monkeypatch.setenv("GAOKAO_ORDERS_DB_PATH", str(tmp_path / "orders.db"))
monkeypatch.setenv("GAOKAO_DB_PATH", str(tmp_path / "admin.db"))
with pytest.raises(RuntimeError, match="admin|默认管理员密码"):
load_settings()