kin: auto-commit after pipeline
This commit is contained in:
parent
0ccd451b4b
commit
04cbbc563b
7 changed files with 324 additions and 7 deletions
179
tests/test_kin_097_regression.py
Normal file
179
tests/test_kin_097_regression.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
"""
|
||||
Regression tests for KIN-097:
|
||||
Tasks should start based on the review/auto toggle state, not independently.
|
||||
|
||||
Root causes fixed:
|
||||
(1) load() now calls loadMode() after reload — toggle syncs with DB
|
||||
(2) runTask() now patches execution_mode before running — task always gets
|
||||
the current toggle state, not a stale value from DB
|
||||
|
||||
Backend regression:
|
||||
- task.execution_mode=auto_complete → pipeline auto-approves (status=done)
|
||||
- task.execution_mode=review → pipeline does NOT auto-approve (status=review),
|
||||
even if project.execution_mode=auto_complete
|
||||
- get_effective_mode uses task-level execution_mode with higher priority than project
|
||||
"""
|
||||
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from core.db import init_db
|
||||
from core import models
|
||||
from agents.runner import run_pipeline
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixtures & helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.fixture
|
||||
def conn():
|
||||
c = init_db(":memory:")
|
||||
models.create_project(c, "p1", "P1", "/tmp/p1", tech_stack=["python"])
|
||||
models.create_task(c, "P1-001", "p1", "Fix bug",
|
||||
brief={"route_type": "debug"})
|
||||
yield c
|
||||
c.close()
|
||||
|
||||
|
||||
def _mock_success(output="done"):
|
||||
m = MagicMock()
|
||||
m.stdout = json.dumps({"result": output})
|
||||
m.stderr = ""
|
||||
m.returncode = 0
|
||||
return m
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_effective_mode: task-level priority regression
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGetEffectiveMode:
|
||||
"""Regression: task.execution_mode has higher priority than project.execution_mode."""
|
||||
|
||||
def test_task_review_overrides_project_auto_complete(self, conn):
|
||||
"""KIN-097: task=review + project=auto_complete → effective mode is 'review'."""
|
||||
models.update_project(conn, "p1", execution_mode="auto_complete")
|
||||
models.update_task(conn, "P1-001", execution_mode="review")
|
||||
mode = models.get_effective_mode(conn, "p1", "P1-001")
|
||||
assert mode == "review", (
|
||||
"task-level review должен override project-level auto_complete"
|
||||
)
|
||||
|
||||
def test_task_auto_complete_overrides_project_review(self, conn):
|
||||
"""KIN-097: task=auto_complete + project=review → effective mode is 'auto_complete'."""
|
||||
models.update_project(conn, "p1", execution_mode="review")
|
||||
models.update_task(conn, "P1-001", execution_mode="auto_complete")
|
||||
mode = models.get_effective_mode(conn, "p1", "P1-001")
|
||||
assert mode == "auto_complete", (
|
||||
"task-level auto_complete должен override project-level review"
|
||||
)
|
||||
|
||||
def test_task_none_falls_back_to_project_auto_complete(self, conn):
|
||||
"""Если task.execution_mode=None, берётся project.execution_mode=auto_complete."""
|
||||
models.update_project(conn, "p1", execution_mode="auto_complete")
|
||||
# task остаётся без execution_mode
|
||||
mode = models.get_effective_mode(conn, "p1", "P1-001")
|
||||
assert mode == "auto_complete"
|
||||
|
||||
def test_task_none_project_none_defaults_to_review(self, conn):
|
||||
"""Если оба None → fallback 'review' (безопасный режим)."""
|
||||
# Проект без execution_mode (default NULL)
|
||||
mode = models.get_effective_mode(conn, "p1", "P1-001")
|
||||
assert mode == "review"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# run_pipeline: autopilot only triggers in auto_complete
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestRunPipelineCompletionMode:
|
||||
"""KIN-097 acceptance criteria: pipeline outcome depends on execution_mode."""
|
||||
|
||||
@patch("core.followup.generate_followups")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_task_review_mode_does_not_auto_approve_when_project_is_auto(
|
||||
self, mock_run, mock_hooks, mock_followup, conn
|
||||
):
|
||||
"""KIN-097 regression: project=auto_complete но task=review → status=review (не done)."""
|
||||
mock_run.return_value = _mock_success()
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_project(conn, "p1", execution_mode="auto_complete")
|
||||
# Frontend патчит task с текущим состоянием тоггла перед run
|
||||
models.update_task(conn, "P1-001", execution_mode="review")
|
||||
|
||||
steps = [{"role": "debugger", "brief": "find bug"},
|
||||
{"role": "tester", "brief": "verify"}]
|
||||
result = run_pipeline(conn, "P1-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
task = models.get_task(conn, "P1-001")
|
||||
assert task["status"] == "review", (
|
||||
"При execution_mode=review задача должна ждать ручного approve, "
|
||||
"а НЕ auto-approve несмотря на project.execution_mode=auto_complete"
|
||||
)
|
||||
|
||||
@patch("core.followup.generate_followups")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_task_auto_complete_auto_approves_when_project_is_review(
|
||||
self, mock_run, mock_hooks, mock_followup, conn
|
||||
):
|
||||
"""KIN-097: project=review но task=auto_complete → status=done (автопилот активен)."""
|
||||
mock_run.return_value = _mock_success()
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
# Проект в review-режиме
|
||||
# Frontend патчит task с текущим состоянием тоггла перед run
|
||||
models.update_task(conn, "P1-001", execution_mode="auto_complete")
|
||||
|
||||
steps = [{"role": "debugger", "brief": "find bug"},
|
||||
{"role": "tester", "brief": "verify"}]
|
||||
result = run_pipeline(conn, "P1-001", steps)
|
||||
|
||||
assert result["success"] is True
|
||||
task = models.get_task(conn, "P1-001")
|
||||
assert task["status"] == "done", (
|
||||
"task.execution_mode=auto_complete должен auto-approve (status=done) "
|
||||
"даже если project.execution_mode=review"
|
||||
)
|
||||
|
||||
@patch("core.followup.generate_followups")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_task_auto_complete_mode_returned_in_result(
|
||||
self, mock_run, mock_hooks, mock_followup, conn
|
||||
):
|
||||
"""run_pipeline включает поле mode=auto_complete в результат."""
|
||||
mock_run.return_value = _mock_success()
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_task(conn, "P1-001", execution_mode="auto_complete")
|
||||
steps = [{"role": "debugger", "brief": "find"},
|
||||
{"role": "tester", "brief": "test"}]
|
||||
result = run_pipeline(conn, "P1-001", steps)
|
||||
|
||||
assert result.get("mode") == "auto_complete"
|
||||
|
||||
@patch("core.followup.generate_followups")
|
||||
@patch("agents.runner.run_hooks")
|
||||
@patch("agents.runner.subprocess.run")
|
||||
def test_task_review_mode_returned_in_result(
|
||||
self, mock_run, mock_hooks, mock_followup, conn
|
||||
):
|
||||
"""run_pipeline включает поле mode=review в результат при review-задаче."""
|
||||
mock_run.return_value = _mock_success()
|
||||
mock_hooks.return_value = []
|
||||
mock_followup.return_value = {"created": [], "pending_actions": []}
|
||||
|
||||
models.update_task(conn, "P1-001", execution_mode="review")
|
||||
steps = [{"role": "debugger", "brief": "find"}]
|
||||
result = run_pipeline(conn, "P1-001", steps)
|
||||
|
||||
assert result.get("mode") == "review"
|
||||
Loading…
Add table
Add a link
Reference in a new issue