From 552bead64b1e3074104766bc2ef0bc9e1e91d9db Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 13 Oct 2026 23:45:47 +0400 Subject: [PATCH] =?UTF-8?q?fix(agent):=20ensure=5Fsprint=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B5=20=D0=B1=D0=B5=D0=B7=20?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D0=B0=20=E2=80=94=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B2=D0=B5=D1=82=20Agile=20=D0=B1=D0=B5=D0=B7=20fields?= =?UTF-8?q?=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jira банка на GET /rest/agile/1.0/issue/{key}?fields=sprint у задачи без спринта отвечает без ключа fields; агент падал с KeyError. Фейк Jira теперь отвечает так же. Co-Authored-By: Claude Opus 5.5 --- src/agent.py | 4 +++- tests/fake_jira.py | 2 ++ tests/test_executor_links.py | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/agent.py b/src/agent.py index 79aeaae..37144eb 100644 --- a/src/agent.py +++ b/src/agent.py @@ -739,7 +739,9 @@ class Executor: def do_ensure_sprint(self, p: dict, cmd: dict) -> dict: key, want = p["key"], p["sprint"] - cur = ((self.jira.get(AGILE + "/issue/%s" % key, fields="sprint")["fields"].get("sprint") or {}).get("name")) + # у задачи без спринта Jira банка отвечает без ключа fields (#13) + issue = self.jira.get(AGILE + "/issue/%s" % key, fields="sprint") + cur = (((issue.get("fields") or {}).get("sprint")) or {}).get("name") if want == "backlog": # в бэклог доска не нужна if cur is None: return self._out("done", {"sprint": None, "changed": False}) diff --git a/tests/fake_jira.py b/tests/fake_jira.py index bef6359..90df453 100644 --- a/tests/fake_jira.py +++ b/tests/fake_jira.py @@ -248,6 +248,8 @@ class FakeJira: if m: name = self.issues[m.group(1)]["sprint"] sp = next((s for s in self.sprints if s["name"] == name), None) + if sp is None: # как Jira банка (13.10.2026): без спринта ключа fields в ответе нет + return 200, {"key": m.group(1)} return 200, {"key": m.group(1), "fields": {"sprint": sp}} return 404, {"errorMessages": ["нет маршрута %s %s" % (method, p)]} diff --git a/tests/test_executor_links.py b/tests/test_executor_links.py index 6fa0073..2b41fbc 100644 --- a/tests/test_executor_links.py +++ b/tests/test_executor_links.py @@ -153,6 +153,16 @@ class ExecutorLinksTest(unittest.TestCase): self.assertEqual(again["result"], {"sprint": "Спринт 2", "changed": False}) self.assertEqual(again["jira_calls"], 1) + def test_sprint_issue_without_sprint_has_no_fields(self): + """#13: у задачи без спринта Jira банка отвечает без ключа fields — это «спринта нет», а не ошибка.""" + self.assertIsNone(self.fake.issues["AURORA-1"]["sprint"]) + backlog = self.run_cmd("ensure_sprint", key="AURORA-1", sprint="backlog") + self.assertEqual(backlog["status"], "done") + self.assertEqual(backlog["result"], {"sprint": None, "changed": False}) + out = self.run_cmd("ensure_sprint", key="AURORA-1", sprint="Спринт 2") + self.assertEqual(out["status"], "done") + self.assertEqual(self.fake.issues["AURORA-1"]["sprint"], "Спринт 2") + def test_sprint_pagination(self): for i in range(3, 58): # 57 спринтов: доска отдаёт по 50, нужны две страницы self.fake.sprints.append({"id": i, "name": "Спринт %d" % i, "state": "future"})