kin: auto-commit after pipeline

This commit is contained in:
Gros Frumos 2026-03-17 21:30:57 +02:00
parent 938f7e3a57
commit 4f1dfbf10f

View file

@ -285,3 +285,138 @@ class TestApiFollowupEmptyArrayNeedsDecision:
) )
assert data["created"] == [] assert data["created"] == []
assert data["pending_actions"] == [] assert data["pending_actions"] == []
# ---------------------------------------------------------------------------
# Additional edge cases — deeper investigation
# ---------------------------------------------------------------------------
class TestRunPipelineNoneSteps:
"""run_pipeline with steps=None — must also return empty_pipeline, not crash."""
@patch("agents.runner.check_claude_auth")
def test_none_steps_returns_empty_pipeline_error(self, mock_auth, conn):
"""run_pipeline(steps=None) must return {success: False, error: 'empty_pipeline'}."""
from agents.runner import run_pipeline
result = run_pipeline(conn, "PROJ-001", None)
assert result["success"] is False
assert result.get("error") == "empty_pipeline", (
f"Expected error='empty_pipeline' for None steps, got: {result}"
)
@patch("agents.runner.check_claude_auth")
def test_none_steps_does_not_mutate_task(self, mock_auth, conn):
"""run_pipeline(steps=None) must not change task status."""
from agents.runner import run_pipeline
before = models.get_task(conn, "PROJ-001")["status"]
run_pipeline(conn, "PROJ-001", None)
after = models.get_task(conn, "PROJ-001")["status"]
assert after == before, (
f"Task status changed from '{before}' to '{after}' after None steps"
)
class TestRunPipelineEmptyStepsDryRun:
"""run_pipeline(steps=[], dry_run=True) — must bail before auth check."""
def test_empty_steps_dry_run_returns_error_without_auth(self, conn):
"""run_pipeline(steps=[], dry_run=True) must return early without auth check."""
from agents.runner import run_pipeline
# No @patch for check_claude_auth — if auth is called, it may raise; empty guard must fire first
result = run_pipeline(conn, "PROJ-001", [], dry_run=True)
assert result["success"] is False
assert result.get("error") == "empty_pipeline"
class TestCliRunTaskNonListPipeline:
"""PM returns pipeline as a non-list non-null value (dict or string) — CLI must exit(1)."""
@patch("agents.runner.run_pipeline")
@patch("agents.runner.run_agent")
def test_dict_pipeline_exits_1(self, mock_run_agent, mock_run_pipeline, tmp_path):
"""PM returning pipeline={} (dict) → CLI exits 1."""
from cli.main import cli as kin_cli
db_path = _seed_db(tmp_path)
mock_run_agent.return_value = {
"success": True,
"output": json.dumps({"pipeline": {"steps": []}, "analysis": "..."}),
}
runner = CliRunner()
result = runner.invoke(kin_cli, ["--db", db_path, "run", "PROJ-001"])
assert result.exit_code == 1, (
f"Expected exit_code=1 for dict pipeline, got {result.exit_code}"
)
mock_run_pipeline.assert_not_called()
@patch("agents.runner.run_pipeline")
@patch("agents.runner.run_agent")
def test_string_pipeline_exits_1(self, mock_run_agent, mock_run_pipeline, tmp_path):
"""PM returning pipeline='[]' (JSON-string-encoded) → CLI exits 1."""
from cli.main import cli as kin_cli
db_path = _seed_db(tmp_path)
mock_run_agent.return_value = {
"success": True,
"output": json.dumps({"pipeline": "[]", "analysis": "..."}),
}
runner = CliRunner()
result = runner.invoke(kin_cli, ["--db", db_path, "run", "PROJ-001"])
assert result.exit_code == 1, (
f"Expected exit_code=1 for string pipeline, got {result.exit_code}"
)
mock_run_pipeline.assert_not_called()
class TestGenerateFollowupsNullAndDict:
"""Additional generate_followups edge cases: null output, dict with empty tasks."""
@patch("agents.runner._run_claude")
def test_agent_returns_null_gives_empty_result(self, mock_claude, conn):
"""generate_followups: agent returning 'null'{created: [], pending_actions: []}."""
from core.followup import generate_followups
mock_claude.return_value = {"output": "null", "returncode": 0}
result = generate_followups(conn, "PROJ-001")
assert result["created"] == [], f"Expected created=[], got: {result['created']}"
assert result["pending_actions"] == []
@patch("agents.runner._run_claude")
def test_agent_returns_null_creates_no_tasks(self, mock_claude, conn):
"""generate_followups: agent returning 'null' must not create any tasks."""
from core.followup import generate_followups
mock_claude.return_value = {"output": "null", "returncode": 0}
tasks_before = conn.execute("SELECT COUNT(*) FROM tasks").fetchone()[0]
generate_followups(conn, "PROJ-001")
tasks_after = conn.execute("SELECT COUNT(*) FROM tasks").fetchone()[0]
assert tasks_after == tasks_before
@patch("agents.runner._run_claude")
def test_agent_returns_dict_with_empty_tasks_list(self, mock_claude, conn):
"""generate_followups: agent returning {"tasks": []} → empty result, no tasks created."""
from core.followup import generate_followups
mock_claude.return_value = {"output": '{"tasks": []}', "returncode": 0}
tasks_before = conn.execute("SELECT COUNT(*) FROM tasks").fetchone()[0]
result = generate_followups(conn, "PROJ-001")
tasks_after = conn.execute("SELECT COUNT(*) FROM tasks").fetchone()[0]
assert result["created"] == []
assert tasks_after == tasks_before
@patch("agents.runner._run_claude")
def test_agent_returns_empty_string_gives_empty_result(self, mock_claude, conn):
"""generate_followups: agent returning '' (empty string) → {created: [], pending_actions: []}."""
from core.followup import generate_followups
mock_claude.return_value = {"output": "", "returncode": 0}
result = generate_followups(conn, "PROJ-001")
assert result["created"] == [], (
f"Expected created=[] for empty string output, got: {result['created']}"
)
assert result["pending_actions"] == []
@patch("agents.runner._run_claude")
def test_agent_returns_whitespace_wrapped_empty_array(self, mock_claude, conn):
"""generate_followups: agent returning ' [] ' (whitespace-wrapped) → no tasks created."""
from core.followup import generate_followups
mock_claude.return_value = {"output": " [] ", "returncode": 0}
result = generate_followups(conn, "PROJ-001")
assert result["created"] == [], (
f"Expected created=[] for whitespace-wrapped '[]', got: {result['created']}"
)
assert result["pending_actions"] == []