kin: KIN-069 Frontend: цветные бейджи категорий и фильтр по категории в канбане

This commit is contained in:
Gros Frumos 2026-03-16 08:41:24 +02:00
parent d627c1ba77
commit 8007960332

View file

@ -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"