kin: KIN-083 Healthcheck claude CLI auth: перед запуском pipeline проверять что claude залогинен (быстрый claude -p 'ok' --output-format json, проверить is_error и 'Not logged in'). Если не залогинен — не запускать pipeline, а показать ошибку 'Claude CLI requires login' в GUI с инструкцией.
This commit is contained in:
parent
a80679ae72
commit
bfc8f1c0bb
18 changed files with 1390 additions and 57 deletions
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
# Patch DB_PATH before importing app
|
||||
import web.api as api_module
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(tmp_path):
|
||||
db_path = tmp_path / "test.db"
|
||||
|
|
@ -224,6 +226,30 @@ def test_run_not_found(client):
|
|||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_run_returns_503_when_claude_not_authenticated(client):
|
||||
"""KIN-083: /run возвращает 503 с claude_auth_required если claude не залогинен."""
|
||||
from agents.runner import ClaudeAuthError
|
||||
with patch("agents.runner.check_claude_auth", side_effect=ClaudeAuthError("Claude CLI requires login. Run: claude login")):
|
||||
r = client.post("/api/tasks/P1-001/run")
|
||||
assert r.status_code == 503
|
||||
body = r.json()
|
||||
assert body["detail"]["error"] == "claude_auth_required"
|
||||
assert body["detail"]["instructions"] == "Run: claude login"
|
||||
assert "login" in body["detail"]["message"].lower()
|
||||
|
||||
|
||||
def test_start_phase_returns_503_when_claude_not_authenticated(client):
|
||||
"""KIN-083: /phases/start возвращает 503 с claude_auth_required если claude не залогинен."""
|
||||
from agents.runner import ClaudeAuthError
|
||||
with patch("agents.runner.check_claude_auth", side_effect=ClaudeAuthError("Claude CLI requires login. Run: claude login")):
|
||||
r = client.post("/api/projects/p1/phases/start")
|
||||
assert r.status_code == 503
|
||||
body = r.json()
|
||||
assert body["detail"]["error"] == "claude_auth_required"
|
||||
assert body["detail"]["instructions"] == "Run: claude login"
|
||||
assert "login" in body["detail"]["message"].lower()
|
||||
|
||||
|
||||
def test_run_kin_038_without_allow_write(client):
|
||||
"""Регрессионный тест KIN-038: allow_write удалён из схемы,
|
||||
эндпоинт принимает запросы с пустым телом без этого параметра."""
|
||||
|
|
@ -1583,3 +1609,89 @@ def test_kin_arch_003_deploy_operations_project_null_path_uses_cwd_none(client):
|
|||
assert call_kwargs.get("cwd") is None, (
|
||||
"KIN-ARCH-003: для operations-проектов без path, cwd должен быть None"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Bootstrap endpoint — KIN-081
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.fixture
|
||||
def bootstrap_client(tmp_path):
|
||||
"""TestClient без seed-данных, с отдельным DB_PATH."""
|
||||
db_path = tmp_path / "bs_test.db"
|
||||
api_module.DB_PATH = db_path
|
||||
from web.api import app
|
||||
return TestClient(app), tmp_path
|
||||
|
||||
|
||||
def test_bootstrap_endpoint_invalid_path_returns_400(bootstrap_client):
|
||||
"""KIN-081: bootstrap возвращает 400 если путь не существует."""
|
||||
client, _ = bootstrap_client
|
||||
r = client.post("/api/bootstrap", json={
|
||||
"id": "newproj", "name": "New Project", "path": "/nonexistent/path/that/does/not/exist"
|
||||
})
|
||||
assert r.status_code == 400
|
||||
assert "not a directory" in r.json()["detail"].lower()
|
||||
|
||||
|
||||
def test_bootstrap_endpoint_duplicate_id_returns_409(bootstrap_client, tmp_path):
|
||||
"""KIN-081: bootstrap возвращает 409 если проект с таким ID уже существует."""
|
||||
client, _ = bootstrap_client
|
||||
proj_dir = tmp_path / "myproj"
|
||||
proj_dir.mkdir()
|
||||
# Create project first
|
||||
client.post("/api/projects", json={"id": "existing", "name": "Existing", "path": str(proj_dir)})
|
||||
# Try bootstrap with same ID
|
||||
r = client.post("/api/bootstrap", json={
|
||||
"id": "existing", "name": "Same ID", "path": str(proj_dir)
|
||||
})
|
||||
assert r.status_code == 409
|
||||
assert "already exists" in r.json()["detail"]
|
||||
|
||||
|
||||
def test_bootstrap_endpoint_rollback_on_save_error(bootstrap_client, tmp_path):
|
||||
"""KIN-081: при ошибке в save_to_db проект удаляется (rollback), возвращается 500."""
|
||||
client, _ = bootstrap_client
|
||||
proj_dir = tmp_path / "rollbackproj"
|
||||
proj_dir.mkdir()
|
||||
|
||||
from core.db import init_db
|
||||
from core import models as _models
|
||||
|
||||
def _save_create_then_fail(conn, project_id, name, path, *args, **kwargs):
|
||||
# Simulate partial write: project row created, then error
|
||||
_models.create_project(conn, project_id, name, path)
|
||||
raise RuntimeError("simulated DB error after project created")
|
||||
|
||||
with patch("web.api.save_to_db", side_effect=_save_create_then_fail):
|
||||
r = client.post("/api/bootstrap", json={
|
||||
"id": "rollbackproj", "name": "Rollback Test", "path": str(proj_dir)
|
||||
})
|
||||
|
||||
assert r.status_code == 500
|
||||
assert "Bootstrap failed" in r.json()["detail"]
|
||||
|
||||
# Project must NOT remain in DB (rollback was executed)
|
||||
conn = init_db(api_module.DB_PATH)
|
||||
assert _models.get_project(conn, "rollbackproj") is None
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_bootstrap_endpoint_success(bootstrap_client, tmp_path):
|
||||
"""KIN-081: успешный bootstrap возвращает 200 с project и counts."""
|
||||
client, _ = bootstrap_client
|
||||
proj_dir = tmp_path / "goodproj"
|
||||
proj_dir.mkdir()
|
||||
(proj_dir / "requirements.txt").write_text("fastapi\n")
|
||||
|
||||
with patch("web.api.find_vault_root", return_value=None):
|
||||
r = client.post("/api/bootstrap", json={
|
||||
"id": "goodproj", "name": "Good Project", "path": str(proj_dir)
|
||||
})
|
||||
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data["project"]["id"] == "goodproj"
|
||||
assert "modules_count" in data
|
||||
assert "decisions_count" in data
|
||||
assert "tasks_count" in data
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue