chore(security): 摘除 knowledge_service 硬编码默认 DeepSeek Key(缺 env 抛明确错误/503)
- knowledge_service: 移除 os.getenv 默认值占位,改 _require_deepseek_key() 必填,缺 env 抛 DeepSeekKeyMissing
- knowledge API: generate/{daily,weekly,monthly} 缺 Key 返回 503(可观测,不再静默走模板兜底)
- finance_bot / verify_zxt_vouchers_json / bot_bridge_push: 凭据改 env 优先(CMA_BOT_KEY_* / CMA_BOT_API_KEYS / CMA_BRIDGE_TOKEN),历史共享键仅作兼容期兜底并告警
- tests: 新增 5 例回归(缺 Key 服务层抛错、API 503;有 Key 正常 200)
(cherry picked from commit 859bd8276d7c5aad4bf10ee3399b6cccc188f064)
This commit is contained in:
committed by
Hermes CI Fix
parent
885cfddbff
commit
a9dcbf22bc
@@ -0,0 +1,92 @@
|
||||
"""knowledge_service 硬编码默认 DeepSeek Key 清理回归(2026-10-18 P2 代码卫生)
|
||||
|
||||
覆盖:
|
||||
1. 缺 DEEPSEEK_API_KEY → 抛 DeepSeekKeyMissing(不再拿假 Key 发请求)
|
||||
2. 有 DEEPSEEK_API_KEY → 正常走请求链路并返回内容
|
||||
3. API 层:缺 Key 时 /knowledge/generate/daily 返回 503 且错误可观测(非静默模板兜底)
|
||||
4. API 层:有 Key 时行为不变(200 + 写入摘要)
|
||||
"""
|
||||
import asyncio
|
||||
import types
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.services import knowledge_service
|
||||
from app.services.knowledge_service import DeepSeekKeyMissing
|
||||
from tests.conftest import create_test_user, get_token_for_user, auth_header
|
||||
|
||||
|
||||
class _FakeResp:
|
||||
status_code = 200
|
||||
|
||||
def json(self):
|
||||
return {"choices": [{"message": {"content": "测试摘要:本期无重大变化。"}}]}
|
||||
|
||||
|
||||
class _FakeAsyncClient:
|
||||
def __init__(self, *a, **kw):
|
||||
pass
|
||||
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *a):
|
||||
return False
|
||||
|
||||
async def post(self, *a, **kw):
|
||||
return _FakeResp()
|
||||
|
||||
|
||||
def _patch_httpx(monkeypatch):
|
||||
"""只替换 knowledge_service 模块内的 httpx 引用,不动全局模块。"""
|
||||
monkeypatch.setattr(
|
||||
knowledge_service, "httpx", types.SimpleNamespace(AsyncClient=_FakeAsyncClient)
|
||||
)
|
||||
|
||||
|
||||
# ── 服务层 ──
|
||||
|
||||
def test_service_raises_when_key_missing(monkeypatch):
|
||||
monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
|
||||
with pytest.raises(DeepSeekKeyMissing) as ei:
|
||||
asyncio.run(knowledge_service._call_deepseek("hello"))
|
||||
assert "DEEPSEEK_API_KEY" in str(ei.value)
|
||||
|
||||
|
||||
def test_service_works_when_key_present(monkeypatch):
|
||||
monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-test-not-a-real-key")
|
||||
_patch_httpx(monkeypatch)
|
||||
out = asyncio.run(knowledge_service._call_deepseek("hello"))
|
||||
assert out == "测试摘要:本期无重大变化。"
|
||||
|
||||
|
||||
def test_require_key_strips_blank(monkeypatch):
|
||||
monkeypatch.setenv("DEEPSEEK_API_KEY", " ")
|
||||
with pytest.raises(DeepSeekKeyMissing):
|
||||
knowledge_service._require_deepseek_key()
|
||||
|
||||
|
||||
# ── API 层 ──
|
||||
|
||||
def test_api_generate_daily_returns_503_without_key(client: TestClient, db: Session, monkeypatch):
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client, username="testadmin")
|
||||
monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
|
||||
|
||||
r = client.post("/api/cma/knowledge/generate/daily", headers=auth_header(token))
|
||||
assert r.status_code == 503, r.text
|
||||
detail = r.json()["detail"]
|
||||
assert "DEEPSEEK_API_KEY" in detail, detail
|
||||
|
||||
|
||||
def test_api_generate_daily_ok_with_key(client: TestClient, db: Session, monkeypatch):
|
||||
create_test_user(db)
|
||||
token = get_token_for_user(client, username="testadmin")
|
||||
monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-test-not-a-real-key")
|
||||
_patch_httpx(monkeypatch)
|
||||
|
||||
r = client.post("/api/cma/knowledge/generate/daily", headers=auth_header(token))
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json()["summary"]["entity_id"] == 1
|
||||
Reference in New Issue
Block a user