fix(commands): проверка типов параметров — 422 вместо 500 (#13)
M-3 (hub#12). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7a2f2e694b
commit
a309c9bfb1
@ -22,6 +22,8 @@ SPEC = {
|
||||
"refresh": {"required": [], "optional": ["keys", "jql"], "keys": ["keys"]},
|
||||
}
|
||||
WRITE_TYPES = [t for t in SPEC if t != "refresh"]
|
||||
LIST_PARAMS = {"labels", "labels_add", "labels_remove", "keys"}
|
||||
BOOL_PARAMS = {"replace"}
|
||||
|
||||
|
||||
class CommandError(ValueError):
|
||||
@ -49,6 +51,23 @@ def temp_keys_used(cmd) -> list[str]:
|
||||
return [k for k in referenced_keys(cmd) if is_temp(k) and k != own]
|
||||
|
||||
|
||||
def _type_error(ctype, name, value) -> str | None:
|
||||
"""Проверка типа значения параметра (M-3 #12): иначе .strip() и подстановка ключей падают с 500.
|
||||
null допустим везде — обязательность проверяется отдельно."""
|
||||
if value is None:
|
||||
return None
|
||||
if name in LIST_PARAMS:
|
||||
ok, want = isinstance(value, list) and all(isinstance(v, str) for v in value), "список строк"
|
||||
elif name in BOOL_PARAMS:
|
||||
ok, want = isinstance(value, bool), "true или false"
|
||||
elif ctype == "ensure_status" and name == "target":
|
||||
ok = isinstance(value, str) or (isinstance(value, list) and all(isinstance(v, str) for v in value))
|
||||
want = "строка или список строк"
|
||||
else:
|
||||
ok, want = isinstance(value, str), "строка"
|
||||
return None if ok else f"{name} — {want}"
|
||||
|
||||
|
||||
def validate(commands) -> list[dict]:
|
||||
if not isinstance(commands, list) or not commands:
|
||||
raise CommandError("пакет без команд")
|
||||
@ -68,6 +87,9 @@ def validate(commands) -> list[dict]:
|
||||
unknown = set(params) - set(spec["required"]) - set(spec["optional"])
|
||||
if unknown:
|
||||
raise CommandError(f"команда {i} ({ctype}): лишние параметры {', '.join(sorted(unknown))}")
|
||||
bad = [e for e in (_type_error(ctype, n, v) for n, v in params.items()) if e]
|
||||
if bad:
|
||||
raise CommandError(f"команда {i} ({ctype}): " + "; ".join(bad))
|
||||
if ctype == "ensure_fields" and not any(f in params for f in spec["optional"]):
|
||||
raise CommandError(f"команда {i}: ensure_fields без полей")
|
||||
if ctype == "refresh" and not (params.get("keys") or params.get("jql")):
|
||||
|
||||
@ -123,3 +123,29 @@ def test_validate_rejects_duplicate_issue_summary():
|
||||
commands.validate(cmds)
|
||||
assert "повторяется тема ensure_issue: «Отчёт»" in str(e.value)
|
||||
commands.validate([cmds[0], cmds[2]]) # регистр различается — пакет допустим
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cmd, name", [
|
||||
({"type": "ensure_issue", "params": {"temp_key": "new:1", "issue_type": "task", "summary": 42}}, "summary"),
|
||||
({"type": "ensure_fields", "params": {"key": "A-1", "labels_add": "AWPCA"}}, "labels_add"),
|
||||
({"type": "ensure_fields", "params": {"key": "A-1", "labels_remove": ["x", 1]}}, "labels_remove"),
|
||||
({"type": "ensure_fields", "params": {"key": ["A-1"], "duedate": "2026-10-23"}}, "key"),
|
||||
({"type": "ensure_comment", "params": {"key": "A-1", "marker": "m", "body": {"t": 1}}}, "body"),
|
||||
({"type": "ensure_status", "params": {"key": "A-1", "target": 5}}, "target"),
|
||||
({"type": "ensure_status", "params": {"key": "A-1", "target": ["Разработка", 2]}}, "target"),
|
||||
({"type": "ensure_link", "params": {"source": "A-1", "target": "A-2", "link_type": "связанные", "replace": "да"}}, "replace"),
|
||||
({"type": "refresh", "params": {"keys": "A-1"}}, "keys"),
|
||||
])
|
||||
def test_validate_rejects_wrong_types(cmd, name):
|
||||
with pytest.raises(commands.CommandError) as e:
|
||||
commands.validate([cmd])
|
||||
assert name in str(e.value)
|
||||
|
||||
|
||||
def test_validate_accepts_null_for_clearable_and_path_target():
|
||||
out = commands.validate([
|
||||
{"type": "ensure_fields", "params": {"key": "A-1", "assignee": None, "epic_key": None, "duedate": None}},
|
||||
{"type": "ensure_status", "params": {"key": "A-1", "target": ["Разработка", "Готово"]}},
|
||||
{"type": "ensure_link", "params": {"source": "A-1", "target": "A-2", "link_type": "связанные", "replace": True}},
|
||||
])
|
||||
assert len(out) == 3
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user