API: состояние агента и синхронизация вики неверной формы — 422, а не 500 (#129, #42 T2.1, #94 T8.1)
mirror.check_issues проверяет форму issues до записи; /wiki/sync отвергает project не строку. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4a6855d70b
commit
f1dae5a811
@ -24,6 +24,7 @@ def h_agent_state(hub, conn, m, q, body):
|
|||||||
issues = body.get("issues") or []
|
issues = body.get("issues") or []
|
||||||
boards = body.get("boards")
|
boards = body.get("boards")
|
||||||
try:
|
try:
|
||||||
|
mirror.check_issues(issues)
|
||||||
mirror.check_comments(issues) # до записи: неверная пачка не применяется целиком
|
mirror.check_comments(issues) # до записи: неверная пачка не применяется целиком
|
||||||
mirror.check_attachments(issues)
|
mirror.check_attachments(issues)
|
||||||
mirror.check_agile_fields(issues)
|
mirror.check_agile_fields(issues)
|
||||||
|
|||||||
@ -32,6 +32,15 @@ def check_comments(issues) -> None:
|
|||||||
if c.get(f) is not None and not isinstance(c[f], str):
|
if c.get(f) is not None and not isinstance(c[f], str):
|
||||||
raise ValueError(f"{where}.{f} — строка или null")
|
raise ValueError(f"{where}.{f} — строка или null")
|
||||||
|
|
||||||
|
def check_issues(issues) -> None:
|
||||||
|
"""Форма списка задач состояния: список объектов с непустым строковым key. Нарушение — ValueError."""
|
||||||
|
if not isinstance(issues, list):
|
||||||
|
raise ValueError("issues — список задач")
|
||||||
|
for n, it in enumerate(issues):
|
||||||
|
if not isinstance(it, dict) or not isinstance(it.get("key"), str) or not it["key"]:
|
||||||
|
raise ValueError(f"issues[{n}] — объект задачи с key")
|
||||||
|
|
||||||
|
|
||||||
def check_attachments(issues) -> None:
|
def check_attachments(issues) -> None:
|
||||||
"""Форма поля attachments у задач состояния (агент v14+); поле необязательно. Нарушение — ValueError с ключом и полем."""
|
"""Форма поля attachments у задач состояния (агент v14+); поле необязательно. Нарушение — ValueError с ключом и полем."""
|
||||||
for it in issues:
|
for it in issues:
|
||||||
|
|||||||
@ -94,6 +94,8 @@ def h_search(hub, conn, m, q, body):
|
|||||||
|
|
||||||
|
|
||||||
def h_sync(hub, conn, m, q, body):
|
def h_sync(hub, conn, m, q, body):
|
||||||
|
if body.get("project") is not None and not isinstance(body["project"], str):
|
||||||
|
raise ApiError(422, "bad_request", "project — строка")
|
||||||
q = dict(q, project=body.get("project"))
|
q = dict(q, project=body.get("project"))
|
||||||
author = body.get("author") or "api"
|
author = body.get("author") or "api"
|
||||||
if not isinstance(author, str):
|
if not isinstance(author, str):
|
||||||
|
|||||||
@ -1299,3 +1299,17 @@ def test_rank_before_trimmed_on_claim(hub):
|
|||||||
{"type": "ensure_rank", "params": {"key": "X-1", "before": " X-2 "}}]})
|
{"type": "ensure_rank", "params": {"key": "X-1", "before": " X-2 "}}]})
|
||||||
cmd = hub["agent"].post("/agent/claim", {})["command"]
|
cmd = hub["agent"].post("/agent/claim", {})["command"]
|
||||||
assert cmd["type"] == "ensure_rank" and cmd["params"]["before"] == "X-2"
|
assert cmd["type"] == "ensure_rank" and cmd["params"]["before"] == "X-2"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("issues, detail", [
|
||||||
|
(["X-1"], "issues[0] — объект задачи с key"),
|
||||||
|
([{"summary": "без ключа"}], "issues[0] — объект задачи с key"),
|
||||||
|
([{"key": 5}], "issues[0] — объект задачи с key"),
|
||||||
|
([{"key": ""}], "issues[0] — объект задачи с key"),
|
||||||
|
({"key": "X-1"}, "issues — список задач"),
|
||||||
|
("X-1", "issues — список задач"),
|
||||||
|
])
|
||||||
|
def test_agent_state_bad_issue_shape_422(hub, issues, detail):
|
||||||
|
with pytest.raises(HubError) as e:
|
||||||
|
hub["agent"].post("/agent/state", {"issues": issues, "links": []})
|
||||||
|
assert e.value.status == 422 and e.value.error == "bad_state" and detail in e.value.detail
|
||||||
|
|||||||
@ -228,3 +228,12 @@ def test_page_attachment_failed_error_visible(api):
|
|||||||
c.close()
|
c.close()
|
||||||
att = api.client.get("/wiki/pages/DOC-3", project="AWPCA")["attachments"][0]
|
att = api.client.get("/wiki/pages/DOC-3", project="AWPCA")["attachments"][0]
|
||||||
assert att["state"] == "failed" and att["error"] == "Eva: 404"
|
assert att["state"] == "failed" and att["error"] == "Eva: 404"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("project", [5, ["AWPCA"], {"a": 1}, True])
|
||||||
|
def test_sync_project_not_string_422(api, project):
|
||||||
|
first_sync(api)
|
||||||
|
before = api.rows("SELECT id FROM batch")
|
||||||
|
e = err(api.client.post, "/wiki/sync", {"project": project})
|
||||||
|
assert e.status == 422 and e.error == "bad_request" and e.detail == "project — строка"
|
||||||
|
assert api.rows("SELECT id FROM batch") == before # пакета не создано
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user