kin/tests/test_kin_097_regression.py
2026-03-16 23:34:22 +02:00

179 lines
7.7 KiB
Python
Raw Permalink 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-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"