From 80079603320002b2b122e03717799d921e951cae Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Mon, 16 Mar 2026 08:41:24 +0200 Subject: [PATCH] =?UTF-8?q?kin:=20KIN-069=20Frontend:=20=D1=86=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=BD=D1=8B=D0=B5=20=D0=B1=D0=B5=D0=B9=D0=B4=D0=B6=D0=B8?= =?UTF-8?q?=20=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=B8=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8=D0=B8=20=D0=B2?= =?UTF-8?q?=20=D0=BA=D0=B0=D0=BD=D0=B1=D0=B0=D0=BD=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_followup.py | 152 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/tests/test_followup.py b/tests/test_followup.py index a385e52..7422680 100644 --- a/tests/test_followup.py +++ b/tests/test_followup.py @@ -312,3 +312,155 @@ class TestAutoResolvePendingActions: results = auto_resolve_pending_actions(conn, "VDOL-001", []) assert results == [] mock_claude.assert_not_called() + + +# --------------------------------------------------------------------------- +# KIN-068 — category наследуется при создании followup и manual задач +# --------------------------------------------------------------------------- + +class TestNextTaskIdWithCategory: + """_next_task_id с category генерирует ID в формате PROJ-CAT-NNN.""" + + @pytest.mark.parametrize("category,expected_prefix", [ + ("SEC", "VDOL-SEC-"), + ("UI", "VDOL-UI-"), + ("API", "VDOL-API-"), + ("INFRA", "VDOL-INFRA-"), + ("BIZ", "VDOL-BIZ-"), + ]) + def test_with_category_produces_cat_format(self, conn, category, expected_prefix): + """_next_task_id с category возвращает PROJ-CAT-NNN.""" + result = _next_task_id(conn, "vdol", category=category) + assert result.startswith(expected_prefix) + suffix = result[len(expected_prefix):] + assert suffix.isdigit() and len(suffix) == 3 + + def test_with_none_category_produces_plain_format(self, conn): + """_next_task_id без category возвращает PROJ-NNN (backward compat).""" + result = _next_task_id(conn, "vdol", category=None) + # VDOL-001 already exists → next is VDOL-002 + assert result == "VDOL-002" + parts = result.split("-") + assert len(parts) == 2 + assert parts[1].isdigit() + + def test_first_cat_task_is_001(self, conn): + """Первая задача категории всегда получает номер 001.""" + result = _next_task_id(conn, "vdol", category="DB") + assert result == "VDOL-DB-001" + + def test_cat_counter_is_per_category(self, conn): + """Счётчик независим для каждой категории.""" + models.create_task(conn, "VDOL-SEC-001", "vdol", "Security task", category="SEC") + assert _next_task_id(conn, "vdol", category="SEC") == "VDOL-SEC-002" + assert _next_task_id(conn, "vdol", category="UI") == "VDOL-UI-001" + + +class TestFollowupCategoryInheritance: + """Регрессионный тест KIN-068: followup задачи наследуют category родителя.""" + + @pytest.mark.parametrize("category", ["SEC", "UI", "API", "INFRA", "BIZ", None]) + @patch("agents.runner._run_claude") + def test_generate_followups_followup_inherits_category( + self, mock_claude, category, conn + ): + """Followup задача наследует category родительской задачи (включая None).""" + # Установить category на родительской задаче + models.update_task(conn, "VDOL-001", category=category) + + mock_claude.return_value = { + "output": json.dumps([ + {"title": "Followup task", "type": "feature", "priority": 3}, + ]), + "returncode": 0, + } + + result = generate_followups(conn, "VDOL-001") + + assert len(result["created"]) == 1 + followup = result["created"][0] + + # category должен совпадать с родительской задачей + assert followup["category"] == category + + # ID должен иметь правильный формат + if category: + assert followup["id"].startswith(f"VDOL-{category}-"), ( + f"Ожидался ID вида VDOL-{category}-NNN, получен {followup['id']!r}" + ) + else: + # Без категории: старый формат VDOL-NNN + parts = followup["id"].split("-") + assert len(parts) == 2, ( + f"Ожидался ID вида VDOL-NNN (2 части), получен {followup['id']!r}" + ) + assert parts[1].isdigit() + + @pytest.mark.parametrize("category", ["SEC", "UI", "API", "INFRA", "BIZ", None]) + def test_resolve_pending_action_manual_task_inherits_category( + self, category, conn + ): + """manual_task при resolve_pending_action наследует category родителя.""" + models.update_task(conn, "VDOL-001", category=category) + + action = { + "type": "permission_fix", + "original_item": { + "title": "Fix manually", + "type": "hotfix", + "priority": 4, + "brief": "Apply permissions fix", + }, + } + result = resolve_pending_action(conn, "VDOL-001", action, "manual_task") + + assert result is not None + assert result["category"] == category + + if category: + assert result["id"].startswith(f"VDOL-{category}-"), ( + f"Ожидался ID вида VDOL-{category}-NNN, получен {result['id']!r}" + ) + else: + parts = result["id"].split("-") + assert len(parts) == 2 + assert parts[1].isdigit() + + @patch("agents.runner._run_claude") + def test_generate_followups_sec_category_id_format(self, mock_claude, conn): + """Регрессионный тест KIN-068: followup задача с category=SEC получает ID VDOL-SEC-001.""" + models.update_task(conn, "VDOL-001", category="SEC") + + mock_claude.return_value = { + "output": json.dumps([{"title": "Fix SQL injection", "priority": 2}]), + "returncode": 0, + } + + result = generate_followups(conn, "VDOL-001") + + assert len(result["created"]) == 1 + followup = result["created"][0] + assert followup["id"] == "VDOL-SEC-001" + assert followup["category"] == "SEC" + + @patch("agents.runner._run_claude") + def test_generate_followups_multiple_followups_same_category(self, mock_claude, conn): + """Несколько followup задач с одной category получают инкрементальные номера.""" + models.update_task(conn, "VDOL-001", category="API") + + mock_claude.return_value = { + "output": json.dumps([ + {"title": "Add auth header", "priority": 2}, + {"title": "Add rate limit", "priority": 3}, + ]), + "returncode": 0, + } + + result = generate_followups(conn, "VDOL-001") + + assert len(result["created"]) == 2 + ids = [t["id"] for t in result["created"]] + assert ids[0] == "VDOL-API-001" + assert ids[1] == "VDOL-API-002" + for t in result["created"]: + assert t["category"] == "API"