"""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"