diff --git a/tests/test_api.py b/tests/test_api.py index 75c87fc..3de2c47 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -168,6 +168,19 @@ def test_revise_not_found(client): assert r.status_code == 404 +def test_revise_task_response_includes_comment(client): + """Ответ /revise содержит поле comment с переданным текстом.""" + r = client.post("/api/tasks/P1-001/revise", json={"comment": "Уточни требования"}) + assert r.status_code == 200 + assert r.json()["comment"] == "Уточни требования" + + +def test_revise_task_missing_comment_returns_422(client): + """Запрос /revise без поля comment → 422 Unprocessable Entity (Pydantic validation).""" + r = client.post("/api/tasks/P1-001/revise", json={}) + assert r.status_code == 422 + + def test_task_pipeline_not_found(client): r = client.get("/api/tasks/NOPE/pipeline") assert r.status_code == 404 diff --git a/tests/test_context_builder.py b/tests/test_context_builder.py index 64bf732..9b78a25 100644 --- a/tests/test_context_builder.py +++ b/tests/test_context_builder.py @@ -161,3 +161,107 @@ class TestLanguageInProject: def test_context_carries_language(self, conn): ctx = build_context(conn, "VDOL-001", "pm", "vdol") assert ctx["project"]["language"] == "ru" + + +# --------------------------------------------------------------------------- +# KIN-045: Revise context — revise_comment + last agent output injection +# --------------------------------------------------------------------------- + +class TestReviseContext: + """build_context и format_prompt корректно инжектируют контекст ревизии.""" + + def test_build_context_includes_revise_comment_in_task(self, conn): + """Если у задачи есть revise_comment, он попадает в ctx['task'].""" + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Доисследуй edge case с пустым массивом",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + assert ctx["task"]["revise_comment"] == "Доисследуй edge case с пустым массивом" + + def test_build_context_fetches_last_agent_output_when_revise_comment_set(self, conn): + """При revise_comment build_context достаёт last_agent_output из agent_logs.""" + from core import models + models.log_agent_run( + conn, "vdol", "developer", "execute", + task_id="VDOL-001", + output_summary="Реализован endpoint POST /api/items", + success=True, + ) + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Добавь валидацию входных данных",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + assert ctx.get("last_agent_output") == "Реализован endpoint POST /api/items" + + def test_build_context_no_last_agent_output_when_no_successful_logs(self, conn): + """revise_comment есть, но нет успешных логов — last_agent_output отсутствует.""" + from core import models + models.log_agent_run( + conn, "vdol", "developer", "execute", + task_id="VDOL-001", + output_summary="Permission denied", + success=False, + ) + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Повтори без ошибок",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + assert "last_agent_output" not in ctx + + def test_build_context_no_revise_fields_when_no_revise_comment(self, conn): + """Обычная задача без revise_comment не получает last_agent_output в контексте.""" + from core import models + models.log_agent_run( + conn, "vdol", "developer", "execute", + task_id="VDOL-001", + output_summary="Всё готово", + success=True, + ) + # revise_comment не устанавливаем + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + assert "last_agent_output" not in ctx + assert ctx["task"].get("revise_comment") is None + + def test_format_prompt_includes_director_revision_request(self, conn): + """format_prompt содержит секцию '## Director's revision request:' при revise_comment.""" + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Обработай случай пустого списка",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + prompt = format_prompt(ctx, "backend_dev", "You are a developer.") + assert "## Director's revision request:" in prompt + assert "Обработай случай пустого списка" in prompt + + def test_format_prompt_includes_previous_output_before_revision(self, conn): + """format_prompt содержит '## Your previous output (before revision):' при last_agent_output.""" + from core import models + models.log_agent_run( + conn, "vdol", "developer", "execute", + task_id="VDOL-001", + output_summary="Сделал миграцию БД", + success=True, + ) + conn.execute( + "UPDATE tasks SET revise_comment=? WHERE id='VDOL-001'", + ("Ещё добавь индекс",), + ) + conn.commit() + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + prompt = format_prompt(ctx, "backend_dev", "You are a developer.") + assert "## Your previous output (before revision):" in prompt + assert "Сделал миграцию БД" in prompt + + def test_format_prompt_no_revision_sections_when_no_revise_comment(self, conn): + """Без revise_comment в prompt нет секций ревизии.""" + ctx = build_context(conn, "VDOL-001", "backend_dev", "vdol") + prompt = format_prompt(ctx, "backend_dev", "You are a developer.") + assert "## Director's revision request:" not in prompt + assert "## Your previous output (before revision):" not in prompt diff --git a/tests/test_runner.py b/tests/test_runner.py index d79746b..61fce2b 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -1412,6 +1412,36 @@ class TestCompletionMode: "KIN-063: project-level 'review' должен применяться когда задача не имеет override" ) + @patch("core.followup.generate_followups") + @patch("agents.runner.run_hooks") + @patch("agents.runner.subprocess.run") + def test_auto_complete_not_broken_by_revise_comment(self, mock_run, mock_hooks, mock_followup, conn): + """Регрессия KIN-045: revise_comment в задаче не ломает auto_complete flow. + + Задача прошла ревизию (revise_comment != None, status=in_progress), + затем повторно запускается пайплайн в auto_complete режиме. + Последний шаг — tester → задача должна получить status='done'. + """ + mock_run.return_value = _mock_claude_success({"result": "all tests pass"}) + mock_hooks.return_value = [] + mock_followup.return_value = {"created": [], "pending_actions": []} + + models.update_project(conn, "vdol", execution_mode="auto_complete") + models.update_task( + conn, "VDOL-001", + status="in_progress", + revise_comment="Добавь тест для пустого массива", + ) + + steps = [{"role": "developer", "brief": "fix"}, {"role": "tester", "brief": "test"}] + result = run_pipeline(conn, "VDOL-001", steps) + + assert result["success"] is True + task = models.get_task(conn, "VDOL-001") + assert task["status"] == "done", ( + "KIN-045: revise_comment не должен мешать auto_complete авто-завершению" + ) + # --------------------------------------------------------------------------- # KIN-048: _run_autocommit — флаг, git path, env=