jira-agent/tests/test_refresh.py

258 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import unittest
from _common import SETTINGS, agent, jira_cfg, no_sleep
from fake_hub import FakeHub
from fake_jira import FakeJira
class RefreshTest(unittest.TestCase):
def setUp(self):
self.fake = FakeJira()
self.hub = FakeHub(SETTINGS)
cfg = jira_cfg(self.fake, hub_url=self.hub.base_url, hub_token=self.hub.token)
self.jira = agent.JiraClient(cfg)
self.jira.sleep = no_sleep
self.ex = agent.Executor(self.jira, dict(SETTINGS), agent.HubClient(cfg))
self.ex.sleep = no_sleep
def tearDown(self):
self.fake.stop()
self.hub.stop()
def test_to_state_and_links(self):
self.fake.add("AURORA-1", assignee="ivanov", customfield_10801="2026-10-20T00:00:00.000+0300",
customfield_15302="AURORA-1350", labels=["AWPCA", "kind-dev"], sprint="Спринт 2")
self.fake.add("AURORA-2")
self.fake.links.append({"id": 1, "type": "ASUPR_Блокер_Начала", "source": "AURORA-1", "target": "AURORA-2"})
raw = self.fake.issue_json("AURORA-1")
st = self.ex.to_state(raw)
self.assertEqual(st["key"], "AURORA-1")
self.assertEqual(st["assignee"], "ivanov")
self.assertEqual(st["planned_start"], "2026-10-20")
self.assertEqual(st["epic_key"], "AURORA-1350")
self.assertEqual(st["labels"], ["AWPCA", "kind-dev"])
self.assertEqual(st["issue_type"], "Задача")
self.assertEqual(st["jira_updated"], "2026-10-01T10:00:00.000+0300")
self.assertIsNone(st["sprint"]) # спринт заполняет do_refresh
self.assertEqual(self.ex.links_from(raw), [{"source_key": "AURORA-1", "target_key": "AURORA-2", "link_type": "ASUPR_Блокер_Начала"}])
self.assertEqual(self.ex.links_from(self.fake.issue_json("AURORA-2")),
[{"source_key": "AURORA-1", "target_key": "AURORA-2", "link_type": "ASUPR_Блокер_Начала"}])
self.assertIn("customfield_15302", self.ex.state_fields())
def test_full_refresh_with_neighbors_and_sprints(self):
for i in range(1, 4):
self.fake.add("AURORA-%d" % i, sprint="Спринт 2" if i < 3 else None)
self.fake.add("OTHER-9", in_base=False)
self.fake.add("AURORA-7", in_base=False)
self.fake.links.append({"id": 1, "type": "ASUPR_Связанные", "source": "AURORA-1", "target": "OTHER-9"})
self.fake.links.append({"id": 2, "type": "ASUPR_Связанные", "source": "AURORA-7", "target": "AURORA-2"})
self.hub.known = ["AURORA-7"]
out = self.ex.run({"id": 5, "type": "refresh", "params": {"jql": "project = AURORA AND labels = AWPCA"}})
self.assertEqual(out["status"], "done")
self.assertEqual(out["result"], {"issues": 3, "neighbors": 1, "pages": 1})
state = self.hub.states[0]
self.assertEqual(state["command_id"], 5)
self.assertEqual(sorted(i["key"] for i in state["issues"]), ["AURORA-1", "AURORA-2", "AURORA-3", "OTHER-9"])
self.assertEqual(state["neighbor_keys"], ["OTHER-9"])
self.assertTrue(state["last_page"])
sprints = {i["key"]: i["sprint"] for i in state["issues"]}
self.assertEqual(sprints["AURORA-1"], "Спринт 2")
self.assertIsNone(sprints["AURORA-3"])
links = {(l["source_key"], l["target_key"]) for l in state["links"]}
self.assertEqual(links, {("AURORA-1", "OTHER-9"), ("AURORA-7", "AURORA-2")})
searches = [r for r in self.fake.requests if r[1] == "/rest/api/2/search"]
self.assertEqual(len(searches), 2) # базовая выборка + дочитывание соседей
def test_refresh_by_keys_pages(self):
for i in range(1, 121):
self.fake.add("AURORA-%d" % i)
keys = ["AURORA-%d" % i for i in range(1, 121)]
out = self.ex.run({"id": 6, "type": "refresh", "params": {"keys": keys}})
self.assertEqual(out["result"], {"issues": 120, "neighbors": 0, "pages": 2})
self.assertEqual(len(self.hub.states), 2)
self.assertEqual(self.hub.states[0]["page"], 1)
self.assertFalse(self.hub.states[0]["last_page"])
self.assertTrue(self.hub.states[1]["last_page"])
self.assertEqual(len(self.hub.states[0]["issues"]) + len(self.hub.states[1]["issues"]), 120)
def test_refresh_empty_keys(self):
out = self.ex.run({"id": 7, "type": "refresh", "params": {"keys": []}})
self.assertEqual(out["result"], {"issues": 0, "neighbors": 0, "pages": 0})
self.assertEqual(self.fake.requests, [])
self.assertEqual(self.hub.states, [])
def test_comments_in_state(self):
self.fake.add("AURORA-1")
self.fake.add("AURORA-2")
self.fake.add_comment("AURORA-1", "первый\nвторая строка", name="petrov", display="Петров Пётр",
created="2026-10-14T10:00:00.000+0300")
self.fake.add_comment("AURORA-1", "второй", name="ivanov", display=None, created="2026-10-14T11:00:00.000+0300",
updated="2026-10-14T12:00:00.000+0300")
self.fake.add_comment("AURORA-1", None, name=None, display=None) # автор удалён, текста нет
out = self.ex.run({"id": 8, "type": "refresh", "params": {"keys": ["AURORA-1", "AURORA-2"]}})
self.assertEqual(out["status"], "done")
by_key = {i["key"]: i for i in self.hub.states[0]["issues"]}
c = by_key["AURORA-1"]["comments"]
self.assertEqual([x["author"] for x in c], ["Петров Пётр", "ivanov", None])
self.assertEqual(c[0], {"id": c[0]["id"], "author": "Петров Пётр", "created": "2026-10-14T10:00:00.000+0300",
"updated": "2026-10-14T10:00:00.000+0300", "body": "первый\nвторая строка"})
self.assertIsInstance(c[0]["id"], str)
self.assertEqual(c[1]["updated"], "2026-10-14T12:00:00.000+0300")
self.assertEqual(c[2]["body"], "")
self.assertEqual(by_key["AURORA-2"]["comments"], [])
self.assertIn("comment", self.ex.state_fields().split(","))
searches = [r for r in self.fake.requests if r[1] == "/rest/api/2/search"]
self.assertEqual(len(searches), 1) # комментарии пришли тем же search
self.assertFalse(any(r[1].endswith("/comment") for r in self.fake.requests))
def test_comments_partial_list_is_read_fully(self):
self.fake.add("AURORA-1")
for n in range(130):
self.fake.add_comment("AURORA-1", "к%d" % n)
self.fake.search_comment_cap = 20
out = self.ex.run({"id": 9, "type": "refresh", "params": {"keys": ["AURORA-1"]}})
self.assertEqual(out["status"], "done")
c = self.hub.states[0]["issues"][0]["comments"]
self.assertEqual(len(c), 130)
self.assertEqual([x["body"] for x in c[:2]] + [c[-1]["body"]], ["к0", "к1", "к129"])
reads = [r for r in self.fake.requests if r == ("GET", "/rest/api/2/issue/AURORA-1/comment")]
self.assertEqual(len(reads), 2) # 100 + 30
def test_comments_field_missing(self):
raw = {"key": "AURORA-1", "fields": {}} # поле comment недоступно
self.assertEqual(self.ex.comments_of(raw), [])
raw = {"key": "AURORA-1", "fields": {"comment": {"comments": [{"id": "5", "body": "x"}]}}} # total нет
self.assertEqual(self.ex.comments_of(raw),
[{"id": "5", "author": None, "created": None, "updated": None, "body": "x"}])
self.assertEqual(self.fake.requests, []) # без дочитывания
def test_refresh_empty_jql_result(self):
out = self.ex.run({"id": 8, "type": "refresh", "params": {"jql": "project = AURORA AND labels = AWPCA"}})
self.assertEqual(out["status"], "done")
self.assertEqual(out["result"], {"issues": 0, "neighbors": 0, "pages": 0})
self.assertEqual(self.hub.states, [])
def test_keys_with_deleted_key(self):
for i in (1, 2, 3, 4):
self.fake.add("AURORA-%d" % i)
out = self.ex.run({"id": 9, "type": "refresh",
"params": {"keys": ["AURORA-1", "AURORA-2", "AURORA-99", "AURORA-3", "AURORA-98"]}})
self.assertEqual(out["status"], "done")
self.assertEqual(out["result"], {"issues": 3, "neighbors": 0, "pages": 1, "missing": ["AURORA-99", "AURORA-98"]})
self.assertEqual(sorted(i["key"] for i in self.hub.states[0]["issues"]), ["AURORA-1", "AURORA-2", "AURORA-3"])
def test_all_keys_400_fails(self):
out = self.ex.run({"id": 14, "type": "refresh", "params": {"keys": ["X-1", "X-2", "X-3"]}})
self.assertEqual(out["status"], "failed")
self.assertIn("HTTP 400", out["error"])
self.assertEqual(self.hub.states, [])
def test_bisect_cost_one_missing_of_100(self):
for i in range(1, 100):
self.fake.add("AURORA-%d" % i)
keys = ["AURORA-%d" % i for i in range(1, 101)] # AURORA-100 отсутствует
out = self.ex.run({"id": 15, "type": "refresh", "params": {"keys": keys}})
self.assertEqual(out["result"]["missing"], ["AURORA-100"])
searches = [r for r in self.fake.requests if r[1] == "/rest/api/2/search"]
self.assertLessEqual(len(searches), 2 * 7 + 1)
def test_missing_deduped_and_truncated(self):
self.fake.add("AURORA-1")
keys = ["AURORA-1"] + ["GONE-%d" % i for i in range(1, 61)] + ["GONE-1"]
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)
self.fake.links.append({"id": 1, "type": "ASUPR_Связанные", "source": "AURORA-1", "target": "GONE-1"})
self.fake.links.append({"id": 2, "type": "ASUPR_Связанные", "source": "AURORA-1", "target": "AURORA-2"})
out = self.ex.run({"id": 10, "type": "refresh", "params": {"jql": "project = AURORA AND labels = AWPCA"}})
self.assertEqual(out["status"], "done")
self.assertEqual(out["result"], {"issues": 1, "neighbors": 1, "pages": 1, "missing": ["GONE-1"]})
self.assertEqual(self.hub.states[0]["neighbor_keys"], ["AURORA-2"])
def test_non_400_error_fails(self):
self.fake.add("AURORA-1")
def boom(jql):
raise agent.HttpError(500, "oops")
self.ex.search_all = boom
out = self.ex.run({"id": 11, "type": "refresh", "params": {"keys": ["AURORA-1"]}})
self.assertEqual(out["status"], "failed")
self.assertIn("500", out["error"])
def test_link_visible_from_both_ends_deduped(self):
self.fake.add("AURORA-1")
self.fake.add("AURORA-2")
self.fake.links.append({"id": 1, "type": "ASUPR_Связанные", "source": "AURORA-1", "target": "AURORA-2"})
self.ex.run({"id": 12, "type": "refresh", "params": {"keys": ["AURORA-1", "AURORA-2"]}})
self.assertEqual(len(self.hub.states[0]["links"]), 1)
def test_many_neighbors_several_requests(self):
self.fake.add("AURORA-1")
for i in range(1, 151):
self.fake.add("NB-%d" % i, in_base=False)
self.fake.links.append({"id": i, "type": "ASUPR_Связанные", "source": "AURORA-1", "target": "NB-%d" % i})
out = self.ex.run({"id": 13, "type": "refresh", "params": {"jql": "project = AURORA AND labels = AWPCA"}})
self.assertEqual(out["result"], {"issues": 1, "neighbors": 150, "pages": 2})
searches = [r for r in self.fake.requests if r[1] == "/rest/api/2/search"]
self.assertEqual(len(searches), 1 + 2) # база + два чанка соседей
def test_sprint_names_paginated(self):
for i in range(1, 121):
self.fake.add("AURORA-%d" % i, sprint="Спринт 2")
names = self.ex.sprint_names(["AURORA-%d" % i for i in range(1, 101)])
self.assertEqual(len(names), 100)
pages = [r for r in self.fake.requests if r[1] == "/rest/agile/1.0/sprint/2/issue"]
self.assertEqual(len(pages), 2)
def test_fake_hub_terminal_result_not_overwritten(self):
cid = self.hub.enqueue("refresh", {"keys": []})
hub = agent.HubClient(jira_cfg(self.fake, hub_url=self.hub.base_url, hub_token=self.hub.token))
hub.result(cid, {"status": "done", "result": {"a": 1}})
hub.result(cid, {"status": "failed", "error": "x"})
self.assertEqual(self.hub.commands[cid]["status"], "done")
self.assertEqual(self.hub.results[cid]["status"], "done")
def test_hub_unreachable_in_refresh_names_hub(self):
self.fake.add("AURORA-1")
self.ex.hub = agent.HubClient({"hub_url": "http://127.0.0.1:1", "hub_token": "t", "timeout": 2})
out = self.ex.run({"id": 1, "type": "refresh", "params": {"keys": ["AURORA-1"]}})
self.assertEqual(out["status"], "failed")
self.assertTrue(out["error"].startswith("хаб: "), out["error"])
self.assertTrue(out["retryable"])
self.assertEqual(out["peer"], "хабом")
def test_hub_4xx_in_refresh_not_retryable(self):
self.fake.add("AURORA-1")
self.ex.hub = agent.HubClient({"hub_url": self.hub.base_url, "hub_token": "wrong-token", "timeout": 5})
out = self.ex.run({"id": 1, "type": "refresh", "params": {"keys": ["AURORA-1"]}})
self.assertEqual(out["status"], "failed")
self.assertTrue(out["error"].startswith("хаб: HTTP 401"), out["error"])
self.assertFalse(out["retryable"])
def test_jira_error_has_no_peer(self):
self.fake.fail_next.append((500, {}))
out = self.ex.run({"id": 1, "type": "refresh", "params": {"jql": "project = AURORA"}})
self.assertTrue(out["retryable"])
self.assertNotIn("peer", out)
if __name__ == "__main__":
unittest.main()