141 lines
5.6 KiB
Python
141 lines
5.6 KiB
Python
"""Regression tests for KIN-127 bug: parent_task_id silently ignored in POST /api/tasks.
|
||
|
||
Root cause: TaskCreate schema was missing parent_task_id field, so child tasks
|
||
created via API had no parent link in DB. Fixed by adding parent_task_id to TaskCreate
|
||
and passing it to models.create_task.
|
||
|
||
These tests use the API end-to-end (no models bypass) to prevent regression.
|
||
"""
|
||
|
||
import pytest
|
||
|
||
|
||
@pytest.fixture
|
||
def client(tmp_path):
|
||
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
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 1. parent_task_id сохраняется при создании задачи через API
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def test_create_task_with_parent_task_id_saves_link(client):
|
||
"""POST /api/tasks с parent_task_id — ссылка на родителя сохраняется в БД."""
|
||
r = client.post("/api/tasks", json={"project_id": "p1", "title": "Parent"})
|
||
assert r.status_code == 200
|
||
parent_id = r.json()["id"]
|
||
|
||
r = client.post("/api/tasks", json={
|
||
"project_id": "p1",
|
||
"title": "Child",
|
||
"parent_task_id": parent_id,
|
||
})
|
||
assert r.status_code == 200
|
||
child = r.json()
|
||
assert child["parent_task_id"] == parent_id
|
||
|
||
|
||
def test_create_task_without_parent_task_id_has_no_parent(client):
|
||
"""POST /api/tasks без parent_task_id — задача создаётся как корневая."""
|
||
r = client.post("/api/tasks", json={"project_id": "p1", "title": "Root task"})
|
||
assert r.status_code == 200
|
||
task = r.json()
|
||
assert task.get("parent_task_id") is None
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 2. /children возвращает задачи, созданные через API с parent_task_id
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def test_children_endpoint_returns_api_created_children(client):
|
||
"""GET /children видит дочерние задачи, созданные через POST /api/tasks."""
|
||
r = client.post("/api/tasks", json={"project_id": "p1", "title": "Parent"})
|
||
parent_id = r.json()["id"]
|
||
|
||
client.post("/api/tasks", json={
|
||
"project_id": "p1", "title": "Child A", "parent_task_id": parent_id,
|
||
})
|
||
client.post("/api/tasks", json={
|
||
"project_id": "p1", "title": "Child B", "parent_task_id": parent_id,
|
||
})
|
||
|
||
r = client.get(f"/api/tasks/{parent_id}/children")
|
||
assert r.status_code == 200
|
||
titles = {c["title"] for c in r.json()}
|
||
assert titles == {"Child A", "Child B"}
|
||
|
||
|
||
def test_children_endpoint_empty_for_task_created_without_parent(client):
|
||
"""GET /children возвращает [] для задачи без дочерних задач."""
|
||
r = client.post("/api/tasks", json={"project_id": "p1", "title": "Leaf"})
|
||
task_id = r.json()["id"]
|
||
|
||
r = client.get(f"/api/tasks/{task_id}/children")
|
||
assert r.status_code == 200
|
||
assert r.json() == []
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 3. Фильтр ?parent_task_id работает с API-созданными дочерними задачами
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def test_list_tasks_parent_filter_with_api_created_children(client):
|
||
"""GET /api/tasks?parent_task_id={id} находит задачи, созданные через API."""
|
||
r = client.post("/api/tasks", json={"project_id": "p1", "title": "Parent"})
|
||
parent_id = r.json()["id"]
|
||
|
||
client.post("/api/tasks", json={
|
||
"project_id": "p1", "title": "Child", "parent_task_id": parent_id,
|
||
})
|
||
client.post("/api/tasks", json={"project_id": "p1", "title": "Other root"})
|
||
|
||
r = client.get(f"/api/tasks?parent_task_id={parent_id}")
|
||
assert r.status_code == 200
|
||
tasks = r.json()
|
||
assert len(tasks) == 1
|
||
assert tasks[0]["title"] == "Child"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 4. End-to-end: PATCH status=done → revising, дочерняя задача создана через API
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def test_patch_done_becomes_revising_when_child_created_via_api(client):
|
||
"""E2E: дочерняя задача создана через POST /api/tasks → parent PATCH done → revising."""
|
||
r = client.post("/api/tasks", json={"project_id": "p1", "title": "Parent"})
|
||
parent_id = r.json()["id"]
|
||
|
||
client.post("/api/tasks", json={
|
||
"project_id": "p1",
|
||
"title": "Child (open)",
|
||
"parent_task_id": parent_id,
|
||
})
|
||
|
||
r = client.patch(f"/api/tasks/{parent_id}", json={"status": "done"})
|
||
assert r.status_code == 200
|
||
assert r.json()["status"] == "revising"
|
||
|
||
|
||
def test_patch_done_when_api_child_is_done(client):
|
||
"""E2E: все дочерние задачи (созданные через API) done → parent PATCH done → done."""
|
||
r = client.post("/api/tasks", json={"project_id": "p1", "title": "Parent"})
|
||
parent_id = r.json()["id"]
|
||
|
||
r = client.post("/api/tasks", json={
|
||
"project_id": "p1",
|
||
"title": "Child",
|
||
"parent_task_id": parent_id,
|
||
})
|
||
child_id = r.json()["id"]
|
||
|
||
client.patch(f"/api/tasks/{child_id}", json={"status": "done"})
|
||
|
||
r = client.patch(f"/api/tasks/{parent_id}", json={"status": "done"})
|
||
assert r.status_code == 200
|
||
assert r.json()["status"] == "done"
|