kin: auto-commit after pipeline

This commit is contained in:
Gros Frumos 2026-03-17 22:09:29 +02:00
parent 4f1dfbf10f
commit c54849cf20
3 changed files with 170 additions and 25 deletions

View file

@ -1,15 +1,13 @@
"""Regression tests for KIN-111 — empty/null pipeline returned by PM agent.
"""Regression tests for KIN-111 — two separate bug batches.
Root cause (two scenarios):
1. PM returns {"pipeline": null} cli/main.py calls len(None) TypeError (crash)
2. PM returns {"pipeline": []} run_pipeline creates empty DB record,
task stuck in 'review' with 0 steps executed
Batch A (empty/null pipeline):
Root cause: PM returns {"pipeline": null} or {"pipeline": []} crashes or hangs.
Fixes required:
cli/main.py: validate pipeline_steps after extraction non-empty list required
agents/runner.py: run_pipeline early-return when steps=[]
Batch B (Deploy button, Settings JSON, Worktrees toggle):
Root cause: Vite dev server intercepts /api/* requests and returns HTML instead of
proxying to FastAPI. Fix: add server.proxy in vite.config.ts.
Coverage:
Batch A coverage:
(1) run_pipeline with steps=[] returns {success: False, error: 'empty_pipeline'}
(2) run_pipeline with steps=[] does NOT transition task to in_progress
(3) run_pipeline with steps=[] does NOT create a pipeline record in DB
@ -20,7 +18,12 @@ Coverage:
(8) generate_followups: agent returns "[]" no tasks created in DB
(9) generate_followups: task has no prior agent_logs Claude still called (no early bail)
(10) API /followup: agent returns "[]" needs_decision is False
"""
Batch B coverage:
(11) GET /api/projects returns Content-Type: application/json (not text/html)
(12) PATCH /api/projects/{id} with worktrees_enabled=True 200, not 400 Bad Request
(13) POST /api/projects/{id}/deploy without deploy config 400 (button blocked correctly)
(14) vite.config.ts has server.proxy for /api proxy to FastAPI (the actual fix)"""
import json
@ -420,3 +423,106 @@ class TestGenerateFollowupsNullAndDict:
f"Expected created=[] for whitespace-wrapped '[]', got: {result['created']}"
)
assert result["pending_actions"] == []
# ---------------------------------------------------------------------------
# Batch B — (11/12/13/14) Deploy button, Settings JSON, Worktrees toggle
# ---------------------------------------------------------------------------
@pytest.fixture
def api_client(tmp_path):
"""TestClient with isolated DB, seeded project."""
import web.api as api_module
api_module.DB_PATH = tmp_path / "test.db"
from web.api import app
from fastapi.testclient import TestClient
c = TestClient(app)
c.post("/api/projects", json={"id": "p1", "name": "P1", "path": "/p1"})
return c
class TestSettingsJsonResponse:
"""(11) GET /api/projects must return JSON content-type, not HTML."""
def test_get_projects_content_type_is_json(self, api_client):
"""GET /api/projects → Content-Type must be application/json."""
r = api_client.get("/api/projects")
assert r.status_code == 200, f"Expected 200, got {r.status_code}"
ct = r.headers.get("content-type", "")
assert "application/json" in ct, (
f"Expected content-type=application/json, got: {ct!r}. "
"If Vite is serving HTML for /api/* requests, the proxy is not configured."
)
def test_get_projects_returns_list_not_html(self, api_client):
"""GET /api/projects → body must be a JSON list, not an HTML string."""
r = api_client.get("/api/projects")
assert r.status_code == 200
data = r.json() # raises JSONDecodeError if HTML was returned
assert isinstance(data, list), f"Expected list, got: {type(data).__name__}"
class TestWorktreesTogglePatch:
"""(12) PATCH /api/projects/{id} with worktrees_enabled=True must return 200."""
def test_patch_worktrees_enabled_true_returns_200(self, api_client):
"""PATCH worktrees_enabled=True → 200, not 400 Bad Request."""
r = api_client.patch("/api/projects/p1", json={"worktrees_enabled": True})
assert r.status_code == 200, (
f"Expected 200 for worktrees_enabled patch, got {r.status_code}: {r.text}"
)
def test_patch_worktrees_enabled_true_persists(self, api_client):
"""PATCH worktrees_enabled=True → project reflects the change."""
api_client.patch("/api/projects/p1", json={"worktrees_enabled": True})
r = api_client.get("/api/projects/p1")
assert r.status_code == 200
assert r.json()["worktrees_enabled"], (
"worktrees_enabled must be truthy after PATCH"
)
def test_patch_worktrees_enabled_false_returns_200(self, api_client):
"""PATCH worktrees_enabled=False → 200, not 400 Bad Request."""
r = api_client.patch("/api/projects/p1", json={"worktrees_enabled": False})
assert r.status_code == 200, (
f"Expected 200 for worktrees_enabled=False patch, got {r.status_code}: {r.text}"
)
class TestDeployButtonBackend:
"""(13) Deploy endpoint must return 400 when no deploy config is set."""
def test_deploy_without_config_returns_400(self, api_client):
"""POST /deploy on unconfigured project → 400 (Deploy button correctly blocked)."""
r = api_client.post("/api/projects/p1/deploy")
assert r.status_code == 400, (
f"Expected 400 when neither deploy_runtime nor deploy_command is set, got {r.status_code}"
)
def test_deploy_not_found_returns_404(self, api_client):
"""POST /deploy on unknown project → 404."""
r = api_client.post("/api/projects/NOPE/deploy")
assert r.status_code == 404
class TestViteProxyConfig:
"""(14) vite.config.ts must have server.proxy configured for /api.
This is the actual fix for KIN-111: without the proxy, Vite serves its
own HTML for /api/* requests in dev mode, causing JSON parse errors.
"""
def test_vite_config_has_api_proxy(self):
"""vite.config.ts must define server.proxy that includes '/api'."""
import pathlib
config_path = pathlib.Path(__file__).parent.parent / "web" / "frontend" / "vite.config.ts"
assert config_path.exists(), f"vite.config.ts not found at {config_path}"
content = config_path.read_text()
assert "proxy" in content, (
"vite.config.ts has no 'proxy' config. "
"Add server: { proxy: { '/api': 'http://localhost:8000' } } to fix "
"the Unexpected token '<' error in Settings and the Bad Request on Worktrees toggle."
)
assert "/api" in content or "'/api'" in content or '"/api"' in content, (
"vite.config.ts proxy must include '/api' route to FastAPI backend."
)