fix(agent): поиск дубля с пагинацией, ошибки настроек (R-11), HTTPException как обрыв, маркер комментария, тесты ревью
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
This commit is contained in:
parent
6bcc6f611f
commit
2e6f47a514
@ -86,7 +86,7 @@ class FakeJira:
|
||||
def issue_json(self, key):
|
||||
f = self.issues[key]
|
||||
fields = {"summary": f["summary"], "description": f["description"], "status": {"name": f["status"]},
|
||||
"priority": {"name": f["priority"]}, "assignee": self._user(f["assignee"]), "reporter": self._user(f["reporter"]),
|
||||
"priority": {"name": f["priority"], "id": int(f["priority"]) if str(f["priority"]).isdigit() else f["priority"]}, "assignee": self._user(f["assignee"]), "reporter": self._user(f["reporter"]),
|
||||
"duedate": f["duedate"], "labels": list(f["labels"]), "issuetype": {"name": f["issuetype"]},
|
||||
"created": f["created"], "updated": f["updated"], "issuelinks": self.links_of(key)}
|
||||
fields.update({c: f.get(c) for c in CUSTOM})
|
||||
|
||||
@ -89,6 +89,37 @@ class ClientsTest(unittest.TestCase):
|
||||
self.jira.get("/x")
|
||||
self.assertEqual(self.jira.calls, 1)
|
||||
|
||||
def test_incomplete_read_is_connection_error(self):
|
||||
import http.client
|
||||
from unittest import mock
|
||||
with mock.patch("urllib.request.urlopen", side_effect=http.client.IncompleteRead(b"ab", 10)):
|
||||
with self.assertRaises(ConnectionError):
|
||||
self.jira.get("/x")
|
||||
|
||||
def test_incomplete_read_inside_response_body(self):
|
||||
import socket
|
||||
import threading
|
||||
srv = socket.socket()
|
||||
srv.bind(("127.0.0.1", 0))
|
||||
srv.listen(1)
|
||||
|
||||
def serve():
|
||||
c, _ = srv.accept()
|
||||
c.recv(4096)
|
||||
c.sendall(b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 100\r\n\r\n{\"a\":")
|
||||
c.close()
|
||||
|
||||
t = threading.Thread(target=serve, daemon=True)
|
||||
t.start()
|
||||
cfg = jira_cfg(self.fake)
|
||||
cfg["jira"]["base_url"] = "http://127.0.0.1:%d" % srv.getsockname()[1]
|
||||
try:
|
||||
with self.assertRaises(ConnectionError):
|
||||
agent.JiraClient(cfg).get("/x")
|
||||
finally:
|
||||
t.join(2)
|
||||
srv.close()
|
||||
|
||||
def test_bearer_header(self):
|
||||
cfg = jira_cfg(self.fake)
|
||||
cfg["jira"]["auth"] = {"type": "bearer", "token": "tok123"}
|
||||
|
||||
@ -104,6 +104,99 @@ class ExecutorFieldsTest(unittest.TestCase):
|
||||
def test_unknown_type(self):
|
||||
self.assertEqual(self.run_cmd("ensure_magic", key="x")["status"], "failed")
|
||||
|
||||
# --- ensure_issue: поиск дубля -----------------------------------------------
|
||||
def test_ensure_issue_finds_on_later_page(self):
|
||||
self.fake.add("AURORA-500", reporter="me", summary="Нужная задача")
|
||||
for i in range(250):
|
||||
self.fake.add("AURORA-%d" % (600 + i), reporter="me", summary="Другая %d" % i)
|
||||
out = self.run_cmd("ensure_issue", temp_key="n:1", issue_type="task", summary="Нужная задача")
|
||||
self.assertEqual(out["result"], {"key": "AURORA-500", "existed": True})
|
||||
searches = [q for q in self.fake.queries if "jql" in q]
|
||||
self.assertEqual(len(searches), 2) # вторая страница
|
||||
self.assertIn("reporter = currentUser()", searches[0]["jql"])
|
||||
self.assertIn("created >= -7d", searches[0]["jql"])
|
||||
self.assertIn("ORDER BY created DESC", searches[0]["jql"])
|
||||
self.assertEqual(searches[0]["fields"], "summary")
|
||||
self.assertEqual(self.fake.writes(), [])
|
||||
|
||||
def test_ensure_issue_substring_is_not_duplicate_and_strip(self):
|
||||
self.fake.add("AURORA-500", reporter="me", summary="Нужная задача (старая)")
|
||||
self.fake.add("AURORA-501", reporter="me", summary=" Точная тема \n")
|
||||
out = self.run_cmd("ensure_issue", temp_key="n:1", issue_type="task", summary="Нужная задача")
|
||||
self.assertTrue(out["result"]["created"])
|
||||
out = self.run_cmd("ensure_issue", temp_key="n:2", issue_type="task", summary="Точная тема")
|
||||
self.assertEqual(out["result"], {"key": "AURORA-501", "existed": True})
|
||||
|
||||
def test_http_error_becomes_failed(self):
|
||||
self.fake.fail_next.append((400, {}))
|
||||
out = self.run_cmd("ensure_issue", temp_key="n:1", issue_type="task", summary="x")
|
||||
self.assertEqual(out["status"], "failed")
|
||||
self.assertIn("HTTP 400", out["error"])
|
||||
self.fake.add("AURORA-1")
|
||||
self.fake.fail_next.append((400, {}))
|
||||
out = self.run_cmd("ensure_comment", key="AURORA-1", marker="М", body="текст")
|
||||
self.assertEqual(out["status"], "failed")
|
||||
|
||||
# --- настройки (R-11) --------------------------------------------------------
|
||||
def with_settings(self, **over):
|
||||
s = dict(SETTINGS)
|
||||
s.update(over)
|
||||
self.ex = agent.Executor(self.jira, s)
|
||||
self.ex.sleep = no_sleep
|
||||
|
||||
def test_epic_key_without_field_id_fails(self):
|
||||
self.fake.add("AURORA-1")
|
||||
self.with_settings(epic_link={"mode": "field"})
|
||||
for ctype, params in (("ensure_issue", dict(temp_key="n", issue_type="task", summary="Э", epic_key="AURORA-9")),
|
||||
("ensure_fields", dict(key="AURORA-1", epic_key="AURORA-9"))):
|
||||
out = self.run_cmd(ctype, **params)
|
||||
self.assertEqual(out["status"], "failed")
|
||||
self.assertIn("epic_link.field_id", out["error"])
|
||||
self.assertEqual(self.fake.writes(), [])
|
||||
|
||||
def test_epic_key_with_link_mode_fails_same_in_both(self):
|
||||
self.fake.add("AURORA-1")
|
||||
self.with_settings(epic_link={"mode": "link", "field_id": "customfield_15302"})
|
||||
a = self.run_cmd("ensure_issue", temp_key="n", issue_type="task", summary="Э2", epic_key="AURORA-9")
|
||||
b = self.run_cmd("ensure_fields", key="AURORA-1", epic_key="AURORA-9")
|
||||
self.assertEqual((a["status"], b["status"]), ("failed", "failed"))
|
||||
self.assertIn("epic_link.mode", a["error"])
|
||||
self.assertIn("epic_link.mode", b["error"])
|
||||
|
||||
def test_planned_without_field_id_fails(self):
|
||||
self.fake.add("AURORA-1")
|
||||
ids = dict(SETTINGS["field_ids"])
|
||||
del ids["planned_start"]
|
||||
self.with_settings(field_ids=ids)
|
||||
a = self.run_cmd("ensure_issue", temp_key="n", issue_type="task", summary="П", planned_start="2026-10-20")
|
||||
b = self.run_cmd("ensure_fields", key="AURORA-1", planned_start="2026-10-20")
|
||||
for out in (a, b):
|
||||
self.assertEqual(out["status"], "failed")
|
||||
self.assertIn("field_ids.planned_start", out["error"])
|
||||
self.assertEqual(self.fake.writes(), [])
|
||||
|
||||
# --- комментарии -------------------------------------------------------------
|
||||
def test_comment_marker_only_at_start(self):
|
||||
self.fake.add("AURORA-1")
|
||||
self.fake.comments["AURORA-1"] = [{"id": "1", "body": "Обсуждали, см. Актуализация 10.10.2026 в письме"}]
|
||||
out = self.run_cmd("ensure_comment", key="AURORA-1", marker="Актуализация 10.10.2026", body="новое")
|
||||
self.assertTrue(out["result"]["created"]) # маркер в середине чужого комментария — не наш
|
||||
again = self.run_cmd("ensure_comment", key="AURORA-1", marker="Актуализация 10.10.2026", body="новое")
|
||||
self.assertEqual(again["result"]["changed"], False)
|
||||
|
||||
def test_comment_found_on_later_page(self):
|
||||
self.fake.add("AURORA-1")
|
||||
self.fake.comments["AURORA-1"] = [{"id": str(i), "body": "чужой %d" % i} for i in range(1, 121)]
|
||||
self.fake.comments["AURORA-1"].append({"id": "999", "body": "*Маркер.* старое"})
|
||||
out = self.run_cmd("ensure_comment", key="AURORA-1", marker="Маркер", body="новое")
|
||||
self.assertEqual(out["result"], {"comment_id": "999", "changed": True})
|
||||
|
||||
def test_priority_compare_by_id_as_string(self):
|
||||
self.fake.add("AURORA-1", priority="3")
|
||||
self.with_settings(priority_value_key="id", priority_map={})
|
||||
out = self.run_cmd("ensure_fields", key="AURORA-1", priority="3")
|
||||
self.assertEqual(out["result"], {"changed": []})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@ -117,6 +117,20 @@ class RefreshTest(unittest.TestCase):
|
||||
out = self.ex.run({"id": 16, "type": "refresh", "params": {"keys": keys}})
|
||||
self.assertEqual(out["result"]["missing"], ["GONE-%d" % i for i in range(1, 51)])
|
||||
|
||||
def test_missing_not_duplicated_when_also_a_neighbor(self):
|
||||
self.fake.add("AURORA-1")
|
||||
self.fake.links.append({"id": 1, "type": "ASUPR_Связанные", "source": "AURORA-1", "target": "GONE-1"})
|
||||
out = self.ex.run({"id": 17, "type": "refresh", "params": {"keys": ["AURORA-1", "GONE-1"]}})
|
||||
self.assertEqual(out["status"], "done")
|
||||
self.assertEqual(out["result"]["missing"], ["GONE-1"])
|
||||
|
||||
def test_input_keys_deduplicated(self):
|
||||
self.fake.add("AURORA-1")
|
||||
out = self.ex.run({"id": 18, "type": "refresh", "params": {"keys": ["AURORA-1", "AURORA-1", "", "AURORA-1"]}})
|
||||
self.assertEqual(out["result"], {"issues": 1, "neighbors": 0, "pages": 1})
|
||||
self.assertEqual(len(self.hub.states[0]["issues"]), 1)
|
||||
self.assertEqual(self.hub.states[0]["agent_version"], agent.AGENT_VERSION)
|
||||
|
||||
def test_deleted_neighbor(self):
|
||||
self.fake.add("AURORA-1")
|
||||
self.fake.add("AURORA-2", in_base=False)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user