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_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") if __name__ == "__main__": unittest.main()