kin/tests/test_kin_110_regression.py
2026-03-17 21:17:19 +02:00

162 lines
6.4 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.

"""Regression tests for KIN-110 — Followup button for blocked tasks.
Verifies:
1. POST /api/tasks/{id}/followup endpoint exists and responds for blocked task
2. Endpoint returns 404 for non-existent task
3. Endpoint works for any task status (not just blocked)
4. Response contains expected fields: created, pending_actions, needs_decision
"""
import json
import pytest
from unittest.mock import patch, MagicMock
import web.api as api_module
@pytest.fixture
def client(tmp_path):
db_path = tmp_path / "test.db"
api_module.DB_PATH = db_path
from web.api import app
from fastapi.testclient import TestClient
c = TestClient(app)
c.post("/api/projects", json={"id": "p1", "name": "P1", "path": "/p1"})
c.post("/api/tasks", json={"project_id": "p1", "title": "Fix auth"})
return c
@pytest.fixture
def blocked_client(tmp_path):
"""Client with a blocked task seeded."""
db_path = tmp_path / "test.db"
api_module.DB_PATH = db_path
from web.api import app
from fastapi.testclient import TestClient
from core.db import init_db
from core import models
c = TestClient(app)
c.post("/api/projects", json={"id": "p1", "name": "P1", "path": "/p1"})
c.post("/api/tasks", json={"project_id": "p1", "title": "Blocked task"})
# Set task to blocked status
conn = init_db(db_path)
models.update_task(conn, "P1-001", status="blocked")
conn.close()
return c
class TestFollowupEndpoint:
"""Tests for POST /api/tasks/{id}/followup endpoint."""
@patch("agents.runner._run_claude")
def test_followup_blocked_task_returns_200(self, mock_claude, blocked_client):
"""POST /followup на blocked-задаче должен вернуть 200."""
mock_claude.return_value = {
"output": json.dumps([
{"title": "Fix dependency", "type": "hotfix", "priority": 2,
"brief": "Add missing dep"},
]),
"returncode": 0,
}
r = blocked_client.post("/api/tasks/P1-001/followup", json={})
assert r.status_code == 200
@patch("agents.runner._run_claude")
def test_followup_returns_created_tasks(self, mock_claude, blocked_client):
"""Ответ /followup содержит список created с созданными задачами."""
mock_claude.return_value = {
"output": json.dumps([
{"title": "Fix dependency", "type": "hotfix", "priority": 2,
"brief": "Add missing dep"},
]),
"returncode": 0,
}
r = blocked_client.post("/api/tasks/P1-001/followup", json={})
assert r.status_code == 200
data = r.json()
assert "created" in data
assert len(data["created"]) == 1
assert data["created"][0]["title"] == "Fix dependency"
@patch("agents.runner._run_claude")
def test_followup_response_has_required_fields(self, mock_claude, blocked_client):
"""Ответ /followup содержит поля created, pending_actions, needs_decision."""
mock_claude.return_value = {"output": "[]", "returncode": 0}
r = blocked_client.post("/api/tasks/P1-001/followup", json={})
assert r.status_code == 200
data = r.json()
assert "created" in data
assert "pending_actions" in data
assert "needs_decision" in data
def test_followup_nonexistent_task_returns_404(self, blocked_client):
"""POST /followup на несуществующую задачу → 404."""
r = blocked_client.post("/api/tasks/NOPE/followup", json={})
assert r.status_code == 404
@patch("agents.runner._run_claude")
def test_followup_dry_run_does_not_create_tasks(self, mock_claude, blocked_client):
"""dry_run=true не создаёт задачи в БД."""
from core.db import init_db
from core import models
mock_claude.return_value = {
"output": json.dumps([
{"title": "Dry run task", "type": "hotfix", "priority": 3,
"brief": "should not be created"},
]),
"returncode": 0,
}
r = blocked_client.post("/api/tasks/P1-001/followup", json={"dry_run": True})
assert r.status_code == 200
# No tasks should be created in DB (only original P1-001 remains)
conn = init_db(api_module.DB_PATH)
tasks = models.list_tasks(conn, project_id="p1")
conn.close()
assert len(tasks) == 1 # Only P1-001, no new tasks
@patch("agents.runner._run_claude")
def test_followup_creates_child_tasks_in_db(self, mock_claude, blocked_client):
"""Созданные followup-задачи сохраняются в БД с parent_task_id."""
from core.db import init_db
from core import models
mock_claude.return_value = {
"output": json.dumps([
{"title": "New dep task", "type": "feature", "priority": 4,
"brief": "Install dependency"},
]),
"returncode": 0,
}
r = blocked_client.post("/api/tasks/P1-001/followup", json={})
assert r.status_code == 200
conn = init_db(api_module.DB_PATH)
tasks = models.list_tasks(conn, project_id="p1")
conn.close()
# Original task + 1 followup
assert len(tasks) == 2
followup = next((t for t in tasks if t["id"] != "P1-001"), None)
assert followup is not None
assert followup["parent_task_id"] == "P1-001"
@patch("agents.runner._run_claude")
def test_followup_pending_actions_for_permission_blocked_items(
self, mock_claude, blocked_client
):
"""Элементы с permission-блокером попадают в pending_actions, не в created."""
mock_claude.return_value = {
"output": json.dumps([
{"title": "Normal task", "type": "hotfix", "priority": 2,
"brief": "Just a fix"},
{"title": "Ручное применение .env",
"type": "hotfix", "priority": 3,
"brief": "Не получили разрешение на запись"},
]),
"returncode": 0,
}
r = blocked_client.post("/api/tasks/P1-001/followup", json={})
assert r.status_code == 200
data = r.json()
assert len(data["created"]) == 1
assert len(data["pending_actions"]) == 1
assert data["needs_decision"] is True