merge: 修 3 个 QA MEDIUM(规则 GET 404 / 空白 content 422 / 未知 tier 422)

This commit is contained in:
Yaojia Wang
2026-06-24 17:51:03 +02:00
6 changed files with 49 additions and 5 deletions

View File

@@ -210,6 +210,16 @@ def _client(app: Any) -> httpx.AsyncClient:
return httpx.AsyncClient(transport=transport, base_url="http://test")
@pytest.mark.asyncio
async def test_list_rules_unknown_project_returns_404() -> None:
# QA MEDIUM 回归GET 规则列表对不存在 project 返 404原返误导性空 200掩盖坏 id
app, _ = _make_app(project_repo=FakeProjectRepo(), gateway=object())
async with _client(app) as client:
resp = await client.get(f"/projects/{uuid.uuid4()}/rules")
assert resp.status_code == 404
assert resp.json()["error"]["code"] == ErrorCode.NOT_FOUND
# ---- 世界观生成 ----

View File

@@ -99,6 +99,19 @@ async def test_create_rule_empty_content_returns_422() -> None:
assert resp.status_code == 422
@pytest.mark.asyncio
async def test_create_rule_whitespace_content_returns_422() -> None:
# QA MEDIUM 回归:纯空白 contentstrip 后为空)应 422不可入库成垃圾行。
client, repo, _session, _project_repo, pid = _make_client()
async with client:
resp = await client.post(
f"/projects/{pid}/rules",
json={"level": "project", "content": " "},
)
assert resp.status_code == 422
assert len(repo.rows) == 0
@pytest.mark.asyncio
async def test_create_rule_unknown_project_returns_404() -> None:
# QA H2 回归:给不存在的 project 加规则应 404不是 500 的 FK 违例逃逸)。

View File

@@ -91,3 +91,16 @@ def test_put_validation_rejects_blank_provider(client: TestClient) -> None:
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

View File

@@ -378,8 +378,11 @@ async def list_world_entities(
async def list_rules(
project_id: uuid.UUID,
repo: RulesReadRepoDep,
project_repo: ProjectRepoDep,
) -> RuleListResponse:
"""规则列表(按读侧顺序)。规则页用。"""
"""规则列表(按读侧顺序)。规则页用。项目不存在 → 404QA MEDIUM此前返误导性空 200"""
if await project_repo.get(STUB_OWNER_ID, project_id) is None:
raise AppError(ErrorCode.NOT_FOUND, f"project not found: {project_id}")
rules = await repo.all_for_project(project_id)
return RuleListResponse(rules=[RuleView(level=r.level, content=r.content) for r in rules])

View File

@@ -6,6 +6,7 @@ snake_case响应一律 **掩码 Key**,绝不含明文。前端经 OpenAPI
from __future__ import annotations
from pydantic import BaseModel, Field
from ww_llm_gateway.types import Tier
class ProviderView(BaseModel):
@@ -41,7 +42,8 @@ class ProviderCredentialInput(BaseModel):
class TierRoutingInput(BaseModel):
"""单条档位路由写入。"""
tier: str = Field(min_length=1)
# tier 限定已知档位 writer/analyst/light未知档位 → 422QA MEDIUM原接受任意字符串
tier: Tier
provider: str = Field(min_length=1)
model: str = Field(min_length=1)
fallback: list[str] = Field(default_factory=list)

View File

@@ -7,18 +7,21 @@ snake_case前端经 OpenAPI 生成 TS 类型消费。改字段 → 前端必
from __future__ import annotations
from typing import Literal
from typing import Annotated, Literal
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, StringConstraints
RuleLevel = Literal["global", "genre", "style", "project"]
# 先 strip 再校验长度:纯空白内容(" "strip 后为空 → 422QA MEDIUM此前被接受入库
RuleContent = Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)]
class RuleCreateRequest(BaseModel):
"""POST /projects/:id/rules新增一条规则。"""
level: RuleLevel = Field(description="规则级别global/genre/style/project越具体越优先")
content: str = Field(min_length=1, description="规则正文")
content: RuleContent = Field(description="规则正文(首尾空白会被裁剪,不可全空白)")
class RuleView(BaseModel):