fix(hub): типы полей тела запроса дают 422, а не 500

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
This commit is contained in:
Andrey Epifancev 2026-10-11 22:23:00 +04:00
parent 34d5714898
commit 600163a9ee
2 changed files with 42 additions and 6 deletions

View File

@ -142,8 +142,12 @@ def h_note_put(hub, conn, m, q, body):
def h_batches_post(hub, conn, m, q, body):
if not body.get("title"):
raise ApiError(422, "bad_batch", "нет title")
if not body.get("title") or not isinstance(body["title"], str):
raise ApiError(422, "bad_batch", "title — непустая строка")
if not isinstance(body.get("author") or "api", str):
raise ApiError(422, "bad_batch", "author — строка")
if not isinstance(body.get("commands"), list):
raise ApiError(422, "bad_batch", "commands — список команд")
try:
return 201, queue.create_batch(conn, body["title"], body.get("author") or "api", body.get("commands"))
except CommandError as e:
@ -166,6 +170,8 @@ def h_batch_cancel(hub, conn, m, q, body):
def h_command_retry(hub, conn, m, q, body):
if not isinstance(body.get("author") or "api", str):
raise ApiError(422, "bad_retry", "author — строка")
try:
return 201, queue.retry_command(conn, _int(m), body.get("author") or "api")
except KeyError:
@ -184,12 +190,19 @@ def h_command_cancel(hub, conn, m, q, body):
def h_refresh(hub, conn, m, q, body):
if not isinstance(body.get("author") or "api", str):
raise ApiError(422, "bad_refresh", "author — строка")
keys = body.get("keys")
if keys is not None and (not isinstance(keys, list) or not all(isinstance(k, str) and k for k in keys)):
raise ApiError(422, "bad_refresh", "keys — список строк")
if body.get("jql") is not None and not isinstance(body["jql"], str):
raise ApiError(422, "bad_refresh", "jql — строка")
if body.get("full"):
params = {"jql": settings.get(conn, "base_jql")}
elif body.get("jql"):
params = {"jql": body["jql"]}
elif body.get("keys"):
params = {"keys": list(body["keys"])}
params = {"keys": keys}
else:
raise ApiError(422, "bad_refresh", "нужно full, jql или keys")
return 201, queue.create_batch(conn, "Обновление", body.get("author") or "api", [{"type": "refresh", "params": params}])
@ -213,9 +226,12 @@ def h_settings_put(hub, conn, m, q, body):
if bad:
raise ApiError(422, "protected", ", ".join(bad))
version = settings.get(conn, "settings_version")
with db.transaction(conn):
for key, value in body.items():
version = settings.set_(conn, key, value)
try:
with db.transaction(conn):
for key, value in body.items():
version = settings.set_(conn, key, value)
except ValueError as e: # запрет на защищённые ключи из settings.set_
raise ApiError(422, "protected", str(e))
return 200, {"settings_version": version}

View File

@ -221,3 +221,23 @@ def test_claim_long_poll_times_out_with_null(hub):
t0 = time.monotonic()
assert hub["agent"].post("/agent/claim", {})["command"] is None
assert 1.0 <= time.monotonic() - t0 < 4
@pytest.mark.parametrize("path,body", [
("/refresh", {"keys": 5}), ("/refresh", {"keys": "A-1"}), ("/refresh", {"keys": [1]}), ("/refresh", {"jql": 5}),
("/batches", {"title": "x", "commands": 5}), ("/batches", {"title": "x", "commands": "x"}),
("/batches", {"title": ["x"], "commands": []}), ("/batches", {"title": "x", "author": 5, "commands": []}),
])
def test_bad_body_shapes_are_422(hub, path, body):
with pytest.raises(HubError) as e:
hub["client"].post(path, body)
assert e.value.status == 422
def test_settings_put_invalid_is_4xx_and_atomic(hub):
c = hub["client"]
v0 = c.get("/settings")["settings_version"]
with pytest.raises(HubError) as e:
c.put("/settings", {"pacing": {"refresh": 3}, "tokens": {}})
assert 400 <= e.value.status < 500
assert c.get("/settings")["settings_version"] == v0