Kairo-Jinkendo/backend/tests/test_ap22f_backlog_epic_hierarchy.py
Lars f33135b87f
All checks were successful
Deploy Development / deploy (push) Successful in 48s
Test Suite / pytest-backend (push) Successful in 3m54s
Test Suite / lint-backend (push) Successful in 3s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 19s
Test Suite / playwright-smoke (push) Successful in 15s
feat(backlog): Epic-Hierarchie mit parent_backlog_id (ADP P2)
Epics als Backlog-Container mit Baum-UI, Convert-Guard und pytest-Abdeckung — Roll-up bleibt für P4 offen.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 09:22:20 +02:00

162 lines
5.0 KiB
Python

"""AP2.2f — Backlog Epic-Hierarchie (parent_backlog_id + item_kind epic)."""
from __future__ import annotations
from tests.factories import provision_user_in_tenant
from tests.test_initiatives_actions import _auth, _create_initiative, _login
def _create_product_initiative(client, token, title="Epic Product"):
created = _create_initiative(
client,
token,
title=title,
archetype_key="initiative.product",
)
assert created.status_code == 201
return created.json()["id"]
def test_create_epic_and_story_under_epic(client):
user = provision_user_in_tenant(tenant_role="admin")
token = _login(client, user)
initiative_id = _create_product_initiative(client, token)
epic = client.post(
f"/api/initiatives/{initiative_id}/backlog",
json={"title": "Checkout Epic", "item_kind": "epic", "status": "accepted"},
headers=_auth(token),
)
assert epic.status_code == 201
epic_id = epic.json()["id"]
assert epic.json()["item_kind"] == "epic"
assert epic.json().get("parent_backlog_id") is None
story = client.post(
f"/api/initiatives/{initiative_id}/backlog",
json={
"title": "Login Story",
"item_kind": "story",
"parent_backlog_id": epic_id,
"status": "accepted",
},
headers=_auth(token),
)
assert story.status_code == 201
assert story.json()["parent_backlog_id"] == epic_id
listed = client.get(
f"/api/initiatives/{initiative_id}/backlog",
headers=_auth(token),
)
assert listed.status_code == 200
by_id = {item["id"]: item for item in listed.json()}
assert by_id[story.json()["id"]]["parent_backlog_id"] == epic_id
def test_epic_cannot_convert_to_action(client):
user = provision_user_in_tenant(tenant_role="admin")
token = _login(client, user)
initiative_id = _create_product_initiative(client, token, title="Epic Convert Block")
epic = client.post(
f"/api/initiatives/{initiative_id}/backlog",
json={"title": "Big Epic", "item_kind": "epic", "status": "accepted"},
headers=_auth(token),
)
epic_id = epic.json()["id"]
converted = client.post(
f"/api/backlog/{epic_id}/convert-to-action",
json={},
headers=_auth(token),
)
assert converted.status_code == 400
assert "Epic" in converted.json()["detail"]
def test_story_under_epic_converts_to_action(client):
user = provision_user_in_tenant(tenant_role="admin")
token = _login(client, user)
initiative_id = _create_product_initiative(client, token, title="Story Convert")
epic = client.post(
f"/api/initiatives/{initiative_id}/backlog",
json={"title": "Epic", "item_kind": "epic", "status": "accepted"},
headers=_auth(token),
)
epic_id = epic.json()["id"]
story = client.post(
f"/api/initiatives/{initiative_id}/backlog",
json={
"title": "Story",
"item_kind": "story",
"parent_backlog_id": epic_id,
"status": "accepted",
},
headers=_auth(token),
)
story_id = story.json()["id"]
converted = client.post(
f"/api/backlog/{story_id}/convert-to-action",
json={},
headers=_auth(token),
)
assert converted.status_code == 201
assert converted.json()["action"]["title"] == "Story"
def test_cannot_delete_epic_with_children(client):
user = provision_user_in_tenant(tenant_role="admin")
token = _login(client, user)
initiative_id = _create_product_initiative(client, token, title="Epic Delete Guard")
epic = client.post(
f"/api/initiatives/{initiative_id}/backlog",
json={"title": "Epic", "item_kind": "epic", "status": "accepted"},
headers=_auth(token),
)
epic_id = epic.json()["id"]
client.post(
f"/api/initiatives/{initiative_id}/backlog",
json={
"title": "Child Story",
"item_kind": "story",
"parent_backlog_id": epic_id,
"status": "new",
},
headers=_auth(token),
)
deleted = client.delete(f"/api/backlog/{epic_id}", headers=_auth(token))
assert deleted.status_code == 400
assert "untergeordneten" in deleted.json()["detail"]
def test_epic_cannot_have_parent_backlog(client):
user = provision_user_in_tenant(tenant_role="admin")
token = _login(client, user)
initiative_id = _create_product_initiative(client, token, title="Epic Parent Guard")
epic_a = client.post(
f"/api/initiatives/{initiative_id}/backlog",
json={"title": "Epic A", "item_kind": "epic", "status": "accepted"},
headers=_auth(token),
)
epic_a_id = epic_a.json()["id"]
epic_b = client.post(
f"/api/initiatives/{initiative_id}/backlog",
json={
"title": "Epic B",
"item_kind": "epic",
"parent_backlog_id": epic_a_id,
"status": "accepted",
},
headers=_auth(token),
)
assert epic_b.status_code == 400