From e1fe41c42859ba44981d6bf4be6e73931c6c1367 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Sat, 21 Mar 2026 08:22:22 +0200 Subject: [PATCH] kin: auto-commit after pipeline --- tests/test_kin_136_auto_return.py | 125 ++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/tests/test_kin_136_auto_return.py b/tests/test_kin_136_auto_return.py index 0f65d9f..b59ca3f 100644 --- a/tests/test_kin_136_auto_return.py +++ b/tests/test_kin_136_auto_return.py @@ -402,3 +402,128 @@ class TestAutoReturnIntegration: assert result.get("auto_returned") is not True task = models.get_task(conn, "P1-001") assert task["status"] == "done" + + @patch("core.followup.generate_followups") + @patch("agents.runner.run_hooks") + @patch("agents.runner.subprocess.run") + def test_exit_condition_strategic_decision_blocks_task( + self, mock_run, mock_hooks, mock_followup, conn + ): + """exit_condition='strategic_decision' → task stays blocked, no auto-return.""" + mock_hooks.return_value = [] + mock_followup.return_value = {"created": [], "pending_actions": []} + + mock_run.return_value = _mock_reviewer( + "changes_requested", + reason="Needs architectural decision from management", + exit_condition="strategic_decision", + ) + + steps = [{"role": "reviewer", "brief": "review"}] + result = run_pipeline(conn, "P1-001", steps) + + assert result["success"] is False + assert result.get("auto_returned") is not True, ( + "strategic_decision должен блокировать auto-return" + ) + task = models.get_task(conn, "P1-001") + assert task["status"] == "blocked", ( + "exit_condition='strategic_decision' → задача должна остаться blocked" + ) + + @patch("core.followup.generate_followups") + @patch("agents.runner.run_hooks") + @patch("agents.runner.subprocess.run") + def test_review_mode_reviewer_rejection_no_auto_return( + self, mock_run, mock_hooks, mock_followup + ): + """Scenario 2: execution_mode='review' + reviewer rejection → no auto-return. + + In review mode, gate check is skipped — task moves to 'review' status + waiting for human approval, never triggering auto-return. + """ + c = init_db(":memory:") + models.create_project(c, "p1", "P1", "/tmp/p1", tech_stack=["python"]) + models.create_task(c, "P1-001", "p1", "Implement feature", + brief={"route_type": "feature"}) + # Explicitly review mode (not auto_complete) + models.update_task(c, "P1-001", execution_mode="review") + + mock_hooks.return_value = [] + mock_followup.return_value = {"created": [], "pending_actions": []} + mock_run.return_value = _mock_reviewer("changes_requested", + reason="Needs work") + + steps = [{"role": "reviewer", "brief": "review"}] + result = run_pipeline(c, "P1-001", steps) + c.close() + + assert result.get("auto_returned") is not True, ( + "В режиме review авто-возврат не должен срабатывать" + ) + # In review mode the pipeline succeeds (all steps run), task waits for human + assert result["success"] is True + + +# --------------------------------------------------------------------------- +# Unit tests: two sequential auto-returns (decision #1082) +# --------------------------------------------------------------------------- + +class TestTwoSequentialAutoReturns: + """KIN-136: return_count increments correctly across two consecutive returns (decision #1082).""" + + def test_two_sequential_auto_returns_increment_count_to_two(self, conn): + """Two _trigger_auto_return calls → return_count = 2 (not reset, not stuck at 1).""" + with patch("agents.runner.run_pipeline") as mock_rp: + mock_rp.return_value = {"success": True, "steps_completed": 2} + + # First auto-return + result1 = _trigger_auto_return( + conn, "P1-001", "p1", pipeline=None, + original_steps=[{"role": "reviewer"}], + gate_role="reviewer", + gate_reason="first rejection", + allow_write=False, + noninteractive=True, + ) + assert result1["should_escalate"] is False + task_after_first = models.get_task(conn, "P1-001") + assert (task_after_first.get("return_count") or 0) == 1, ( + "После первого авто-возврата return_count должен быть 1" + ) + + # Second auto-return (without resetting count) + result2 = _trigger_auto_return( + conn, "P1-001", "p1", pipeline=None, + original_steps=[{"role": "reviewer"}], + gate_role="reviewer", + gate_reason="second rejection", + allow_write=False, + noninteractive=True, + ) + assert result2["should_escalate"] is False + task_after_second = models.get_task(conn, "P1-001") + assert (task_after_second.get("return_count") or 0) == 2, ( + "После второго авто-возврата return_count должен быть 2 (не сброшен в 1)" + ) + + def test_return_count_not_reset_between_sequential_auto_returns(self, conn): + """Ensures return_count accumulates across calls — cumulative, not per-run (decision #1082).""" + with patch("agents.runner.run_pipeline") as mock_rp: + mock_rp.return_value = {"success": True, "steps_completed": 2} + + for call_number in range(1, _AUTO_RETURN_MAX): + _trigger_auto_return( + conn, "P1-001", "p1", pipeline=None, + original_steps=[{"role": "reviewer"}], + gate_role="reviewer", + gate_reason=f"rejection #{call_number}", + allow_write=False, + noninteractive=True, + ) + + task = models.get_task(conn, "P1-001") + assert (task.get("return_count") or 0) == _AUTO_RETURN_MAX - 1, ( + f"После {_AUTO_RETURN_MAX - 1} авто-возвратов return_count должен быть " + f"{_AUTO_RETURN_MAX - 1} — не сброшен и не потерян" + )