kin: KIN-021 Аудит-лог для --dangerously-skip-permissions в auto mode
This commit is contained in:
parent
67071c757d
commit
a0b0976d8d
16 changed files with 1477 additions and 14 deletions
|
|
@ -608,6 +608,42 @@ def test_run_kin_040_allow_write_true_ignored(client):
|
|||
# KIN-058 — регрессионный тест: stderr=DEVNULL у Popen в web API
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# KIN-020 — manual_escalation задачи: PATCH status='done' резолвит задачу
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_patch_manual_escalation_task_to_done(client):
|
||||
"""PATCH status='done' на manual_escalation задаче — статус обновляется — KIN-020."""
|
||||
from core.db import init_db
|
||||
from core import models
|
||||
conn = init_db(api_module.DB_PATH)
|
||||
models.create_task(conn, "P1-002", "p1", "Fix .dockerignore manually",
|
||||
brief={"task_type": "manual_escalation",
|
||||
"source": "followup:P1-001",
|
||||
"description": "Ручное применение .dockerignore"})
|
||||
conn.close()
|
||||
|
||||
r = client.patch("/api/tasks/P1-002", json={"status": "done"})
|
||||
assert r.status_code == 200
|
||||
assert r.json()["status"] == "done"
|
||||
|
||||
|
||||
def test_manual_escalation_task_brief_preserved_after_patch(client):
|
||||
"""PATCH не затирает brief.task_type — поле manual_escalation сохраняется — KIN-020."""
|
||||
from core.db import init_db
|
||||
from core import models
|
||||
conn = init_db(api_module.DB_PATH)
|
||||
models.create_task(conn, "P1-002", "p1", "Fix manually",
|
||||
brief={"task_type": "manual_escalation",
|
||||
"source": "followup:P1-001"})
|
||||
conn.close()
|
||||
|
||||
client.patch("/api/tasks/P1-002", json={"status": "done"})
|
||||
r = client.get("/api/tasks/P1-002")
|
||||
assert r.status_code == 200
|
||||
assert r.json()["brief"]["task_type"] == "manual_escalation"
|
||||
|
||||
|
||||
def test_run_sets_stderr_devnull(client):
|
||||
"""Регрессионный тест KIN-058: stderr=DEVNULL всегда устанавливается в Popen,
|
||||
чтобы stderr дочернего процесса не загрязнял логи uvicorn."""
|
||||
|
|
@ -626,3 +662,186 @@ def test_run_sets_stderr_devnull(client):
|
|||
"Регрессия KIN-058: stderr у Popen должен быть DEVNULL, "
|
||||
"иначе вывод агента попадает в логи uvicorn"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# KIN-065 — PATCH /api/projects/{id} — autocommit_enabled toggle
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_patch_project_autocommit_enabled_true(client):
|
||||
"""PATCH с autocommit_enabled=true → 200, поле установлено в 1."""
|
||||
r = client.patch("/api/projects/p1", json={"autocommit_enabled": True})
|
||||
assert r.status_code == 200
|
||||
assert r.json()["autocommit_enabled"] == 1
|
||||
|
||||
|
||||
def test_patch_project_autocommit_enabled_false(client):
|
||||
"""После включения PATCH с autocommit_enabled=false → 200, поле установлено в 0."""
|
||||
client.patch("/api/projects/p1", json={"autocommit_enabled": True})
|
||||
r = client.patch("/api/projects/p1", json={"autocommit_enabled": False})
|
||||
assert r.status_code == 200
|
||||
assert r.json()["autocommit_enabled"] == 0
|
||||
|
||||
|
||||
def test_patch_project_autocommit_persisted_via_sql(client):
|
||||
"""После PATCH autocommit_enabled=True прямой SQL подтверждает значение 1."""
|
||||
client.patch("/api/projects/p1", json={"autocommit_enabled": True})
|
||||
|
||||
from core.db import init_db
|
||||
conn = init_db(api_module.DB_PATH)
|
||||
row = conn.execute("SELECT autocommit_enabled FROM projects WHERE id = 'p1'").fetchone()
|
||||
conn.close()
|
||||
assert row is not None
|
||||
assert row[0] == 1
|
||||
|
||||
|
||||
def test_patch_project_autocommit_false_persisted_via_sql(client):
|
||||
"""После PATCH autocommit_enabled=False прямой SQL подтверждает значение 0."""
|
||||
client.patch("/api/projects/p1", json={"autocommit_enabled": True})
|
||||
client.patch("/api/projects/p1", json={"autocommit_enabled": False})
|
||||
|
||||
from core.db import init_db
|
||||
conn = init_db(api_module.DB_PATH)
|
||||
row = conn.execute("SELECT autocommit_enabled FROM projects WHERE id = 'p1'").fetchone()
|
||||
conn.close()
|
||||
assert row is not None
|
||||
assert row[0] == 0
|
||||
|
||||
|
||||
def test_patch_project_autocommit_null_before_first_update(client):
|
||||
"""Новый проект имеет autocommit_enabled=NULL/0 (falsy) до первого обновления."""
|
||||
client.post("/api/projects", json={"id": "p_new", "name": "New", "path": "/new"})
|
||||
|
||||
from core.db import init_db
|
||||
conn = init_db(api_module.DB_PATH)
|
||||
row = conn.execute("SELECT autocommit_enabled FROM projects WHERE id = 'p_new'").fetchone()
|
||||
conn.close()
|
||||
assert row is not None
|
||||
assert not row[0] # DEFAULT 0 или NULL — в любом случае falsy
|
||||
|
||||
|
||||
def test_patch_project_empty_body_returns_400(client):
|
||||
"""PATCH проекта без полей → 400."""
|
||||
r = client.patch("/api/projects/p1", json={})
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
def test_patch_project_not_found(client):
|
||||
"""PATCH несуществующего проекта → 404."""
|
||||
r = client.patch("/api/projects/NOPE", json={"autocommit_enabled": True})
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_patch_project_autocommit_and_execution_mode_together(client):
|
||||
"""PATCH с autocommit_enabled и execution_mode → оба поля обновлены."""
|
||||
r = client.patch("/api/projects/p1", json={
|
||||
"autocommit_enabled": True,
|
||||
"execution_mode": "auto_complete",
|
||||
})
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data["autocommit_enabled"] == 1
|
||||
assert data["execution_mode"] == "auto_complete"
|
||||
|
||||
|
||||
def test_patch_project_returns_full_project_object(client):
|
||||
"""PATCH возвращает полный объект проекта с id, name и autocommit_enabled."""
|
||||
r = client.patch("/api/projects/p1", json={"autocommit_enabled": True})
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data["id"] == "p1"
|
||||
assert data["name"] == "P1"
|
||||
assert "autocommit_enabled" in data
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# KIN-008 — PATCH priority и route_type задачи
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_patch_task_priority(client):
|
||||
"""PATCH priority задачи обновляет поле и возвращает задачу."""
|
||||
r = client.patch("/api/tasks/P1-001", json={"priority": 3})
|
||||
assert r.status_code == 200
|
||||
assert r.json()["priority"] == 3
|
||||
|
||||
|
||||
def test_patch_task_priority_persisted(client):
|
||||
"""После PATCH priority повторный GET возвращает новое значение."""
|
||||
client.patch("/api/tasks/P1-001", json={"priority": 7})
|
||||
r = client.get("/api/tasks/P1-001")
|
||||
assert r.status_code == 200
|
||||
assert r.json()["priority"] == 7
|
||||
|
||||
|
||||
def test_patch_task_priority_invalid_zero(client):
|
||||
"""PATCH с priority=0 → 400."""
|
||||
r = client.patch("/api/tasks/P1-001", json={"priority": 0})
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
def test_patch_task_priority_invalid_eleven(client):
|
||||
"""PATCH с priority=11 → 400."""
|
||||
r = client.patch("/api/tasks/P1-001", json={"priority": 11})
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
def test_patch_task_route_type_set(client):
|
||||
"""PATCH route_type сохраняет значение в brief."""
|
||||
r = client.patch("/api/tasks/P1-001", json={"route_type": "feature"})
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data["brief"]["route_type"] == "feature"
|
||||
|
||||
|
||||
def test_patch_task_route_type_all_valid(client):
|
||||
"""Все допустимые route_type принимаются."""
|
||||
for rt in ("debug", "feature", "refactor", "hotfix"):
|
||||
r = client.patch("/api/tasks/P1-001", json={"route_type": rt})
|
||||
assert r.status_code == 200, f"route_type={rt} rejected"
|
||||
assert r.json()["brief"]["route_type"] == rt
|
||||
|
||||
|
||||
def test_patch_task_route_type_invalid(client):
|
||||
"""Недопустимый route_type → 400."""
|
||||
r = client.patch("/api/tasks/P1-001", json={"route_type": "unknown"})
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
def test_patch_task_route_type_clear(client):
|
||||
"""PATCH route_type='' очищает поле из brief."""
|
||||
client.patch("/api/tasks/P1-001", json={"route_type": "debug"})
|
||||
r = client.patch("/api/tasks/P1-001", json={"route_type": ""})
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
brief = data.get("brief")
|
||||
if brief:
|
||||
assert "route_type" not in brief
|
||||
|
||||
|
||||
def test_patch_task_route_type_merges_brief(client):
|
||||
"""route_type сохраняется вместе с другими полями brief без перезаписи."""
|
||||
from core.db import init_db
|
||||
from core import models
|
||||
conn = init_db(api_module.DB_PATH)
|
||||
models.update_task(conn, "P1-001", brief={"extra": "data"})
|
||||
conn.close()
|
||||
|
||||
r = client.patch("/api/tasks/P1-001", json={"route_type": "hotfix"})
|
||||
assert r.status_code == 200
|
||||
brief = r.json()["brief"]
|
||||
assert brief["route_type"] == "hotfix"
|
||||
assert brief["extra"] == "data"
|
||||
|
||||
|
||||
def test_patch_task_priority_and_route_type_together(client):
|
||||
"""PATCH может обновить priority и route_type одновременно."""
|
||||
r = client.patch("/api/tasks/P1-001", json={"priority": 2, "route_type": "refactor"})
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert data["priority"] == 2
|
||||
assert data["brief"]["route_type"] == "refactor"
|
||||
|
||||
|
||||
def test_patch_task_empty_body_still_returns_400(client):
|
||||
"""Пустое тело по-прежнему возвращает 400 (регрессия KIN-008)."""
|
||||
r = client.patch("/api/tasks/P1-001", json={})
|
||||
assert r.status_code == 400
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue