rename_note: при old_key == new_key ранний выход (DELETE раньше стирал единственную заметку); DELETE и UPDATE выполняются в транзакции. set_note: чтение, слияние и запись в одной транзакции (соединение общее для потоков). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
95 lines
5.0 KiB
Python
95 lines
5.0 KiB
Python
import pytest
|
||
|
||
from jira_hub import db, mirror
|
||
|
||
|
||
def issue(key, **over):
|
||
base = {"key": key, "id": "1", "issue_type": "Задача", "summary": f"Тема {key}", "description": "h3. Контекст",
|
||
"status": "Ожидание разработки", "priority": "Средний", "assignee": None, "reporter": "me",
|
||
"duedate": "2026-10-23", "planned_start": None, "planned_end": "2026-10-23", "epic_key": "AURORA-1350",
|
||
"labels": ["AWPCA"], "sprint": "Спринт 2", "created": "2026-10-10T12:00:00.000+0300",
|
||
"jira_updated": "2026-10-11T12:00:00.000+0300"}
|
||
base.update(over)
|
||
return base
|
||
|
||
|
||
def test_apply_state_upserts_and_replaces_links():
|
||
conn = db.connect(":memory:")
|
||
links = [{"source_key": "A-1", "target_key": "A-2", "link_type": "ASUPR_Блокер_Начала"}]
|
||
out = mirror.apply_state(conn, [issue("A-1"), issue("A-2")], links)
|
||
assert out == {"issues": 2, "links": 1}
|
||
mirror.apply_state(conn, [issue("A-1", summary="Новая тема")],
|
||
[{"source_key": "A-1", "target_key": "A-2", "link_type": "ASUPR_Связанные"}])
|
||
got = mirror.get_issue(conn, "A-1")
|
||
assert got["summary"] == "Новая тема" and got["labels"] == ["AWPCA"]
|
||
assert got["links_out"] == [{"target_key": "A-2", "link_type": "ASUPR_Связанные"}]
|
||
assert mirror.get_issue(conn, "A-2")["links_in"] == [{"source_key": "A-1", "link_type": "ASUPR_Связанные"}]
|
||
|
||
|
||
def test_links_from_other_source_survive():
|
||
conn = db.connect(":memory:")
|
||
mirror.apply_state(conn, [issue("A-1"), issue("A-2")],
|
||
[{"source_key": "A-2", "target_key": "A-1", "link_type": "ASUPR_Связанные"}])
|
||
mirror.apply_state(conn, [issue("A-1")], []) # пришла только A-1 без связей
|
||
assert mirror.get_issue(conn, "A-1")["links_in"] == [{"source_key": "A-2", "link_type": "ASUPR_Связанные"}]
|
||
|
||
|
||
def test_apply_state_marks_neighbors():
|
||
conn = db.connect(":memory:")
|
||
mirror.apply_state(conn, [issue("A-1"), issue("B-9")], [], neighbor_keys={"B-9"})
|
||
assert mirror.get_issue(conn, "B-9")["neighbor"] is True
|
||
assert mirror.get_issue(conn, "A-1")["neighbor"] is False
|
||
mirror.apply_state(conn, [issue("B-9")], []) # вошла в базовую JQL
|
||
assert mirror.get_issue(conn, "B-9")["neighbor"] is False
|
||
assert mirror.known_keys(conn) == ["A-1", "B-9"]
|
||
|
||
|
||
def test_note_survives_refresh_and_renames():
|
||
conn = db.connect(":memory:")
|
||
mirror.set_note(conn, "new:7", {"fact_status": "В работе", "work_kind": "разработка"}, "import")
|
||
mirror.rename_note(conn, "new:7", "A-5")
|
||
mirror.apply_state(conn, [issue("A-5")], [])
|
||
got = mirror.get_issue(conn, "A-5")
|
||
assert got["note"]["fact_status"] == "В работе" and got["note"]["updated_by"] == "import"
|
||
assert mirror.get_note(conn, "new:7") is None
|
||
with pytest.raises(ValueError):
|
||
mirror.set_note(conn, "A-5", {"summary": "нельзя"}, "x")
|
||
|
||
|
||
def test_list_issues_filters():
|
||
conn = db.connect(":memory:")
|
||
mirror.apply_state(conn, [issue("A-1", sprint="Спринт 2", assignee="ivanov", duedate="2026-10-20"),
|
||
issue("A-2", sprint="Спринт 1", labels=["AWPCA", "fact-done"]),
|
||
issue("A-3", sprint="Спринт 2", duedate=None)], [])
|
||
mirror.set_note(conn, "A-3", {"work_kind": "тестирование"}, "x")
|
||
keys = [i["key"] for i in mirror.list_issues(conn, {"sprint": "Спринт 2"})]
|
||
assert keys == ["A-1", "A-3"] # без срока — последней
|
||
assert [i["key"] for i in mirror.list_issues(conn, {"label": "fact-done"})] == ["A-2"]
|
||
assert [i["key"] for i in mirror.list_issues(conn, {"work_kind": "тестирование"})] == ["A-3"]
|
||
assert [i["key"] for i in mirror.list_issues(conn, {"q": "A-2"})] == ["A-2"]
|
||
assert mirror.list_issues(conn, {"assignee": "ivanov"})[0]["work_kind"] is None
|
||
assert len(mirror.list_issues(conn, None, limit=1)) == 1
|
||
|
||
|
||
def test_rename_note_same_key_keeps_note():
|
||
conn = db.connect(":memory:")
|
||
mirror.apply_state(conn, [issue("A-1")], [])
|
||
mirror.set_note(conn, "A-1", {"fact_status": "В работе"}, "x")
|
||
mirror.rename_note(conn, "A-1", "A-1")
|
||
assert mirror.get_note(conn, "A-1")["fact_status"] == "В работе"
|
||
|
||
|
||
def test_rename_note_missing_is_noop():
|
||
conn = db.connect(":memory:")
|
||
mirror.rename_note(conn, "new:404", "A-9") # заметки нет — не падает
|
||
assert mirror.get_note(conn, "new:404") is None
|
||
assert mirror.get_note(conn, "A-9") is None
|
||
|
||
|
||
def test_set_note_partial_keeps_other_fields():
|
||
conn = db.connect(":memory:")
|
||
mirror.set_note(conn, "A-1", {"work_kind": "разработка", "done_text": "сделано"}, "x")
|
||
got = mirror.set_note(conn, "A-1", {"fact_status": "Готово"}, "y")
|
||
assert got["work_kind"] == "разработка" and got["done_text"] == "сделано"
|
||
assert got["fact_status"] == "Готово" and got["updated_by"] == "y"
|