Агент: оценка story_points в ensure_fields (#49)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
514e98e819
commit
255784c858
21
src/agent.py
21
src/agent.py
@ -641,6 +641,15 @@ def _nl(value) -> str:
|
||||
return (value or "").replace("\r\n", "\n")
|
||||
|
||||
|
||||
def _same_points(a, b) -> bool:
|
||||
"""Равны ли оценки story_points: None равно только None; числа — с допуском 1e-9 (3 == 3.0); bool — не число."""
|
||||
if a is None or b is None:
|
||||
return a is None and b is None
|
||||
if isinstance(a, bool) or isinstance(b, bool) or not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
|
||||
return False
|
||||
return abs(a - b) < 1e-9
|
||||
|
||||
|
||||
class ConfigGap(Exception):
|
||||
"""В настройках хаба нет того, что нужно команде (R-11): команда падает с понятным текстом, а не молча теряет поле."""
|
||||
|
||||
@ -846,6 +855,15 @@ class Executor:
|
||||
# --- ensure_fields ---------------------------------------------------------
|
||||
def do_ensure_fields(self, p: dict, cmd: dict) -> dict:
|
||||
key = p["key"]
|
||||
sp_fid = None
|
||||
if "story_points" in p:
|
||||
# Настроенный id берётся без GET /field (как в refresh он не нужен); иначе — поиск в jira_fields.
|
||||
sp_fid = (self.s.get("field_ids") or {}).get("story_points") or self.jira_fields()["story_points"]
|
||||
if not sp_fid:
|
||||
raise ConfigGap("поле оценки не найдено в Jira: задайте field_ids.story_points")
|
||||
want_sp = p["story_points"]
|
||||
if want_sp is not None and (isinstance(want_sp, bool) or not isinstance(want_sp, (int, float)) or not want_sp >= 0):
|
||||
return self._out("failed", error="story_points — число ≥ 0 или null")
|
||||
cur = self.jira.get(API + "/issue/%s" % key)["fields"]
|
||||
fields, changed = {}, []
|
||||
if "summary" in p and (cur.get("summary") or "") != (p["summary"] or ""):
|
||||
@ -884,6 +902,9 @@ class Executor:
|
||||
if new != labels:
|
||||
fields[self.fid("labels")] = new
|
||||
changed.append("labels")
|
||||
if sp_fid and not _same_points(cur.get(sp_fid), p["story_points"]):
|
||||
fields[sp_fid] = p["story_points"]
|
||||
changed.append("story_points")
|
||||
if not fields:
|
||||
return self._out("done", {"changed": []})
|
||||
self.jira.put(API + "/issue/%s" % key, {"fields": fields})
|
||||
|
||||
@ -259,6 +259,40 @@ class ExecutorFieldsTest(unittest.TestCase):
|
||||
out = self.run_cmd("ensure_fields", key="AURORA-1", priority="3")
|
||||
self.assertEqual(out["result"], {"changed": []})
|
||||
|
||||
# --- story_points (Task 4, #49) ----------------------------------------------
|
||||
def test_story_points_set_clear_idempotent(self):
|
||||
self.fake.add("AURORA-1")
|
||||
out = self.run_cmd("ensure_fields", key="AURORA-1", story_points=3)
|
||||
self.assertEqual(out["result"], {"changed": ["story_points"]})
|
||||
self.assertEqual(self.fake.issues["AURORA-1"]["customfield_10106"], 3)
|
||||
for v in (3, 3.0):
|
||||
again = self.run_cmd("ensure_fields", key="AURORA-1", story_points=v)
|
||||
self.assertEqual(again["result"], {"changed": []})
|
||||
self.assertEqual(len(self.fake.writes()), 1)
|
||||
self.run_cmd("ensure_fields", key="AURORA-1", story_points=None)
|
||||
self.assertIsNone(self.fake.issues["AURORA-1"]["customfield_10106"])
|
||||
|
||||
def test_story_points_no_field(self): # сценарий «Нет поля оценки»
|
||||
self.fake.add("AURORA-1")
|
||||
self.fake.fields = []
|
||||
out = self.run_cmd("ensure_fields", key="AURORA-1", story_points=3)
|
||||
self.assertEqual((out["status"], out["error"]), ("failed", "поле оценки не найдено в Jira: задайте field_ids.story_points"))
|
||||
self.assertEqual(self.fake.writes(), [])
|
||||
|
||||
def test_story_points_override(self):
|
||||
self.fake.add("AURORA-1")
|
||||
self.ex.s["field_ids"] = dict(SETTINGS["field_ids"], story_points="customfield_20000")
|
||||
self.run_cmd("ensure_fields", key="AURORA-1", story_points=5)
|
||||
self.assertEqual(self.fake.issues["AURORA-1"]["customfield_20000"], 5)
|
||||
self.assertNotIn(("GET", "/rest/api/2/field"), self.fake.requests)
|
||||
|
||||
def test_story_points_bad_value(self):
|
||||
self.fake.add("AURORA-1")
|
||||
for v in ("3", -1, True):
|
||||
out = self.run_cmd("ensure_fields", key="AURORA-1", story_points=v)
|
||||
self.assertEqual((out["status"], out["error"]), ("failed", "story_points — число ≥ 0 или null"), v)
|
||||
self.assertEqual(self.fake.writes(), [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user