kin/tests/test_kin_biz_002.py

200 lines
8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Regression tests for KIN-BIZ-002.
Проблема: approve через /tasks/{id}/approve не продвигал phase state machine.
Фикс: в approve_task() добавлен блок, вызывающий approve_phase() из core.phases
если задача принадлежит активной фазе.
В approve_phase() endpoint добавлена синхронизация task.status='done'.
Тесты покрывают:
1. POST /tasks/{id}/approve для phase-задачи → phase.status=done, следующая фаза active
2. Изменения в БД персистентны после approve
3. POST /tasks/{id}/approve для обычной задачи → не ломает ничего, phase=None
4. POST /phases/{id}/approve → task.status синхронизируется в done
"""
import pytest
from fastapi.testclient import TestClient
import web.api as api_module
@pytest.fixture
def client(tmp_path):
"""Изолированная временная БД для каждого теста."""
db_path = tmp_path / "test_biz002.db"
api_module.DB_PATH = db_path
from web.api import app
return TestClient(app)
def _create_project_with_phases(client, project_id: str = "proj_biz002") -> dict:
"""Вспомогательная: создаёт проект с двумя researcher-фазами + architect."""
r = client.post("/api/projects/new", json={
"id": project_id,
"name": "BIZ-002 Test Project",
"path": f"/tmp/{project_id}",
"description": "Тест регрессии KIN-BIZ-002",
"roles": ["business_analyst", "tech_researcher"],
})
assert r.status_code == 200, r.json()
return r.json()
def _get_active_phase(client, project_id: str) -> dict:
"""Вспомогательная: возвращает первую активную фазу."""
phases = client.get(f"/api/projects/{project_id}/phases").json()
active = next(ph for ph in phases if ph["status"] == "active")
return active
# ---------------------------------------------------------------------------
# KIN-BIZ-002 — регрессионные тесты
# ---------------------------------------------------------------------------
def test_KIN_BIZ_002_approve_task_advances_phase_state_machine(client):
"""KIN-BIZ-002: POST /tasks/{id}/approve для phase-задачи продвигает state machine.
Ожидаем: phase.status=approved, next_phase активирован.
"""
_create_project_with_phases(client)
active_phase = _get_active_phase(client, "proj_biz002")
task_id = active_phase["task_id"]
r = client.post(f"/api/tasks/{task_id}/approve", json={})
assert r.status_code == 200
data = r.json()
assert data["status"] == "done"
# Ключ phase должен присутствовать и содержать результат
assert "phase" in data
assert data["phase"] is not None
# Одобренная фаза имеет status=approved
assert data["phase"]["phase"]["status"] == "approved"
# Следующая фаза была активирована
assert data["phase"]["next_phase"] is not None
assert data["phase"]["next_phase"]["status"] == "active"
def test_KIN_BIZ_002_approve_task_phase_status_persists_in_db(client):
"""KIN-BIZ-002: после approve через /tasks/{id}/approve статусы фаз корректны в БД.
Первая фаза → approved, вторая фаза → active.
"""
data = _create_project_with_phases(client)
# Три фазы: business_analyst, tech_researcher, architect
assert len(data["phases"]) == 3
active_phase = _get_active_phase(client, "proj_biz002")
task_id = active_phase["task_id"]
client.post(f"/api/tasks/{task_id}/approve", json={})
# Перечитываем фазы из БД
phases = client.get("/api/projects/proj_biz002/phases").json()
statuses = {ph["role"]: ph["status"] for ph in phases}
assert statuses["business_analyst"] == "approved"
assert statuses["tech_researcher"] == "active"
assert statuses["architect"] == "pending"
def test_KIN_BIZ_002_approve_task_task_status_is_done(client):
"""KIN-BIZ-002: сама задача должна иметь status=done после approve."""
_create_project_with_phases(client)
active_phase = _get_active_phase(client, "proj_biz002")
task_id = active_phase["task_id"]
client.post(f"/api/tasks/{task_id}/approve", json={})
task = client.get(f"/api/tasks/{task_id}").json()
assert task["status"] == "done"
def test_KIN_BIZ_002_approve_regular_task_does_not_affect_phases(client):
"""KIN-BIZ-002: approve обычной задачи (без фазы) не ломает ничего, phase=None."""
# Создаём обычный проект без фаз
client.post("/api/projects", json={
"id": "plain_proj",
"name": "Plain Project",
"path": "/tmp/plain_proj",
})
r_task = client.post("/api/tasks", json={
"project_id": "plain_proj",
"title": "Обычная задача без фазы",
})
assert r_task.status_code == 200
task_id = r_task.json()["id"]
r = client.post(f"/api/tasks/{task_id}/approve", json={})
assert r.status_code == 200
data = r.json()
assert data["status"] == "done"
# phase должен быть None — нет связанной фазы
assert data["phase"] is None
def test_KIN_BIZ_002_approve_regular_task_sets_status_done(client):
"""KIN-BIZ-002: approve обычной задачи корректно устанавливает status=done."""
client.post("/api/projects", json={
"id": "plain2",
"name": "Plain2",
"path": "/tmp/plain2",
})
r_task = client.post("/api/tasks", json={
"project_id": "plain2",
"title": "Задача без фазы",
})
task_id = r_task.json()["id"]
client.post(f"/api/tasks/{task_id}/approve", json={})
task = client.get(f"/api/tasks/{task_id}").json()
assert task["status"] == "done"
def test_KIN_BIZ_002_approve_phase_endpoint_syncs_task_status_to_done(client):
"""KIN-BIZ-002: POST /phases/{id}/approve синхронизирует task.status=done.
Гарантируем консистентность обоих путей одобрения фазы.
"""
_create_project_with_phases(client)
active_phase = _get_active_phase(client, "proj_biz002")
phase_id = active_phase["id"]
task_id = active_phase["task_id"]
r = client.post(f"/api/phases/{phase_id}/approve", json={})
assert r.status_code == 200
# Задача, связанная с фазой, должна иметь status=done
task = client.get(f"/api/tasks/{task_id}").json()
assert task["status"] == "done"
def test_KIN_BIZ_002_full_phase_chain_two_approves_completes_workflow(client):
"""KIN-BIZ-002: последовательный approve через /tasks/{id}/approve проходит весь chain.
business_analyst → approved → tech_researcher → approved → architect → approved.
"""
_create_project_with_phases(client)
phases_init = client.get("/api/projects/proj_biz002/phases").json()
assert len(phases_init) == 3
# Апруваем каждую фазу последовательно через task-endpoint
for _ in range(3):
phases = client.get("/api/projects/proj_biz002/phases").json()
active = next((ph for ph in phases if ph["status"] == "active"), None)
if active is None:
break
task_id = active["task_id"]
r = client.post(f"/api/tasks/{task_id}/approve", json={})
assert r.status_code == 200
assert r.json()["status"] == "done"
# После всех approve все фазы должны быть approved
final_phases = client.get("/api/projects/proj_biz002/phases").json()
for ph in final_phases:
assert ph["status"] == "approved", (
f"Ожидали approved для {ph['role']}, получили {ph['status']}"
)