Files
writer-work-flow/apps/api/tests/test_settings_providers.py
Yaojia Wang 3868f80502 fix(qa): 修 3 个 QA MEDIUM——规则/档位路由校验收紧
- 规则 GET 不存在 project → 404(原返误导性空 200,掩盖坏 id):list_rules 加
  项目存在校验(仿 C1/H2)。
- 规则 content 纯空白 → 422(原 strip 前校验 min_length,空白行入库成垃圾):
  改 StringConstraints(strip_whitespace=True, min_length=1)。
- 档位路由 tier 限定 writer/analyst/light(Tier Literal)→ 未知档位 422
  (原接受任意字符串)。

回归测试:test_generation(GET rules 404)/ test_rules(空白 content 422)/
test_settings_providers(未知 tier 422)。门禁绿:ruff/format/mypy(210)/pytest。
2026-06-24 17:51:02 +02:00

107 lines
3.6 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.

"""T1.7 端点测试:凭据 upsert/列表/探测(内存替身,无 DB/无网络)。
DoD掩码往返、test-connection 经可注入探测回能力矩阵、明文绝不出响应。
"""
from __future__ import annotations
from fakes_providers import FakeCredentialStore, FakeProviderProbe
from fastapi.testclient import TestClient
from ww_api.security.credentials import decrypt_api_key
from ww_api.services.credentials import STUB_OWNER_ID
def test_get_empty_providers(client: TestClient) -> None:
resp = client.get("/settings/providers")
assert resp.status_code == 200
body = resp.json()
assert body == {"providers": [], "tier_routing": []}
def test_put_credential_encrypts_and_masks(
client: TestClient, store: FakeCredentialStore, enc_key: str
) -> None:
# Act
resp = client.put(
"/settings/providers",
json={"credentials": [{"provider": "deepseek", "api_key": "sk-secret-9999"}]},
)
# Assert: 响应含掩码,绝不含明文
assert resp.status_code == 200
body = resp.json()
assert "sk-secret-9999" not in resp.text
assert body["providers"][0]["provider"] == "deepseek"
# 存的是密文且能解回明文(往返)
blob = store.creds[(STUB_OWNER_ID, "deepseek")]
assert b"sk-secret-9999" not in blob
assert decrypt_api_key(blob, key=enc_key) == "sk-secret-9999"
def test_put_is_idempotent(client: TestClient, store: FakeCredentialStore) -> None:
payload = {"credentials": [{"provider": "deepseek", "api_key": "sk-a"}]}
client.put("/settings/providers", json=payload)
client.put(
"/settings/providers", json={"credentials": [{"provider": "deepseek", "api_key": "sk-b"}]}
)
# 同 (owner, provider) 只一条
assert len([k for k in store.creds if k[1] == "deepseek"]) == 1
def test_put_tier_routing(client: TestClient, store: FakeCredentialStore) -> None:
resp = client.put(
"/settings/providers",
json={
"tier_routing": [
{
"tier": "writer",
"provider": "deepseek",
"model": "deepseek-chat",
"fallback": ["kimi"],
}
]
},
)
assert resp.status_code == 200
routing = resp.json()["tier_routing"]
assert routing[0] == {
"tier": "writer",
"provider": "deepseek",
"model": "deepseek-chat",
"fallback": ["kimi"],
}
def test_test_connection_returns_capabilities(client: TestClient, probe: FakeProviderProbe) -> None:
resp = client.post("/settings/providers/test", json={"provider": "deepseek"})
assert resp.status_code == 200
body = resp.json()
assert body["provider"] == "deepseek"
assert body["ok"] is True
assert body["capabilities"] == {
"structured_output": True,
"prefix_cache": True,
"thinking": False,
}
assert probe.calls == [(STUB_OWNER_ID, "deepseek")]
def test_put_validation_rejects_blank_provider(client: TestClient) -> None:
resp = client.put(
"/settings/providers",
json={"credentials": [{"provider": "", "api_key": "sk-x"}]},
)
assert resp.status_code == 422
def test_put_validation_rejects_unknown_tier(client: TestClient) -> None:
# QA MEDIUM 回归tier 限定 writer/analyst/light未知档位 → 422原接受任意字符串
resp = client.put(
"/settings/providers",
json={
"tier_routing": [
{"tier": "bogus", "provider": "deepseek", "model": "x", "fallback": []}
]
},
)
assert resp.status_code == 422