All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 2m58s
Test Suite / lint-backend (push) Successful in 4s
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 1m1s
Offene APs können beim Abschluss in den Eingang oder einen anderen Sprint überführt werden; abgeschlossene Sprints sind reaktivierbar. Co-authored-by: Cursor <cursoragent@cursor.com>
247 lines
7.4 KiB
Python
247 lines
7.4 KiB
Python
"""AP2.2e — Unplan, Bug/Issue mit Feature-Referenz, Sprint-Lifecycle."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from tests.factories import provision_user_in_tenant
|
|
from tests.test_initiatives_actions import _auth, _create_initiative, _login
|
|
|
|
|
|
def test_unplan_action_restores_backlog(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
|
|
created = _create_initiative(
|
|
client,
|
|
token,
|
|
title="Sprint Planung E2E",
|
|
archetype_key="initiative.product",
|
|
)
|
|
initiative_id = created.json()["id"]
|
|
|
|
cycle = client.post(
|
|
f"/api/initiatives/{initiative_id}/work-cycles",
|
|
json={"title": "Sprint 1", "status": "planned"},
|
|
headers=_auth(token),
|
|
)
|
|
assert cycle.status_code == 201
|
|
cycle_id = cycle.json()["id"]
|
|
|
|
backlog = client.post(
|
|
f"/api/initiatives/{initiative_id}/backlog",
|
|
json={"title": "Story A", "status": "accepted"},
|
|
headers=_auth(token),
|
|
)
|
|
assert backlog.status_code == 201
|
|
backlog_id = backlog.json()["id"]
|
|
|
|
converted = client.post(
|
|
f"/api/backlog/{backlog_id}/convert-to-action",
|
|
json={"work_cycle_id": cycle_id, "assign_active_sprint": False},
|
|
headers=_auth(token),
|
|
)
|
|
assert converted.status_code == 201
|
|
action_id = converted.json()["action"]["id"]
|
|
assert converted.json()["backlog_item"]["status"] == "converted"
|
|
|
|
unplan = client.post(
|
|
f"/api/actions/{action_id}/unplan-to-backlog",
|
|
headers=_auth(token),
|
|
)
|
|
assert unplan.status_code == 200
|
|
body = unplan.json()
|
|
assert body["backlog_item"]["status"] == "accepted"
|
|
assert body["backlog_item"]["id"] == backlog_id
|
|
assert body["backlog_item"]["converted_action_id"] is None
|
|
assert body["action"]["status"] == "discarded"
|
|
assert body["action"]["work_cycle_id"] is None
|
|
|
|
|
|
def test_unplan_rejects_in_progress(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
|
|
created = _create_initiative(
|
|
client,
|
|
token,
|
|
title="Unplan Guard",
|
|
archetype_key="initiative.product",
|
|
)
|
|
initiative_id = created.json()["id"]
|
|
|
|
action = client.post(
|
|
f"/api/initiatives/{initiative_id}/actions",
|
|
json={"title": "Laufendes AP", "status": "in_progress"},
|
|
headers=_auth(token),
|
|
)
|
|
assert action.status_code == 201
|
|
action_id = action.json()["id"]
|
|
|
|
unplan = client.post(
|
|
f"/api/actions/{action_id}/unplan-to-backlog",
|
|
headers=_auth(token),
|
|
)
|
|
assert unplan.status_code == 400
|
|
|
|
|
|
def test_backlog_bug_with_feature_reference(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
|
|
created = _create_initiative(
|
|
client,
|
|
token,
|
|
title="Bug Ref",
|
|
archetype_key="initiative.product",
|
|
)
|
|
initiative_id = created.json()["id"]
|
|
|
|
cycle = client.post(
|
|
f"/api/initiatives/{initiative_id}/work-cycles",
|
|
json={"title": "Sprint 1", "status": "planned"},
|
|
headers=_auth(token),
|
|
)
|
|
assert cycle.status_code == 201
|
|
cycle_id = cycle.json()["id"]
|
|
|
|
feature = client.post(
|
|
f"/api/initiatives/{initiative_id}/actions",
|
|
json={"title": "Login Feature", "action_kind": "delivery"},
|
|
headers=_auth(token),
|
|
)
|
|
assert feature.status_code == 201
|
|
feature_id = feature.json()["id"]
|
|
|
|
bug = client.post(
|
|
f"/api/initiatives/{initiative_id}/backlog",
|
|
json={
|
|
"title": "Login-Button kaputt",
|
|
"status": "accepted",
|
|
"item_kind": "bug",
|
|
"parent_action_id": feature_id,
|
|
},
|
|
headers=_auth(token),
|
|
)
|
|
assert bug.status_code == 201
|
|
assert bug.json()["item_kind"] == "bug"
|
|
assert bug.json()["parent_action_id"] == feature_id
|
|
|
|
converted = client.post(
|
|
f"/api/backlog/{bug.json()['id']}/convert-to-action",
|
|
json={"work_cycle_id": cycle_id, "assign_active_sprint": False},
|
|
headers=_auth(token),
|
|
)
|
|
assert converted.status_code == 201
|
|
action = converted.json()["action"]
|
|
assert action["action_kind"] == "bug"
|
|
assert action["parent_action_id"] == feature_id
|
|
assert action["work_cycle_id"] == cycle_id
|
|
|
|
|
|
def test_complete_sprint_reports_open_actions(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
|
|
created = _create_initiative(
|
|
client,
|
|
token,
|
|
title="Complete Stats",
|
|
archetype_key="initiative.product",
|
|
)
|
|
initiative_id = created.json()["id"]
|
|
|
|
cycle = client.post(
|
|
f"/api/initiatives/{initiative_id}/work-cycles",
|
|
json={"title": "Sprint Active", "status": "active"},
|
|
headers=_auth(token),
|
|
)
|
|
assert cycle.status_code == 201
|
|
cycle_id = cycle.json()["id"]
|
|
|
|
client.post(
|
|
f"/api/initiatives/{initiative_id}/actions",
|
|
json={"title": "Offen", "work_cycle_id": cycle_id, "status": "open"},
|
|
headers=_auth(token),
|
|
)
|
|
|
|
completed = client.post(
|
|
f"/api/initiatives/{initiative_id}/work-cycles/{cycle_id}/complete",
|
|
headers=_auth(token),
|
|
)
|
|
assert completed.status_code == 200
|
|
assert completed.json()["status"] == "reached"
|
|
assert completed.json()["open_action_count_at_completion"] == 1
|
|
|
|
|
|
def test_reactivate_completed_sprint(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
|
|
created = _create_initiative(
|
|
client,
|
|
token,
|
|
title="Reactivate Sprint",
|
|
archetype_key="initiative.product",
|
|
)
|
|
initiative_id = created.json()["id"]
|
|
|
|
cycle = client.post(
|
|
f"/api/initiatives/{initiative_id}/work-cycles",
|
|
json={"title": "Sprint R1", "status": "active"},
|
|
headers=_auth(token),
|
|
)
|
|
cycle_id = cycle.json()["id"]
|
|
|
|
completed = client.post(
|
|
f"/api/initiatives/{initiative_id}/work-cycles/{cycle_id}/complete",
|
|
headers=_auth(token),
|
|
)
|
|
assert completed.status_code == 200
|
|
assert completed.json()["status"] == "reached"
|
|
|
|
reactivated = client.post(
|
|
f"/api/initiatives/{initiative_id}/work-cycles/{cycle_id}/reactivate",
|
|
headers=_auth(token),
|
|
)
|
|
assert reactivated.status_code == 200
|
|
assert reactivated.json()["status"] == "active"
|
|
|
|
active = client.get(
|
|
f"/api/initiatives/{initiative_id}/work-cycles/active",
|
|
headers=_auth(token),
|
|
)
|
|
assert active.status_code == 200
|
|
assert active.json()["id"] == cycle_id
|
|
|
|
|
|
def test_carryover_unplan_allows_in_progress(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
|
|
created = _create_initiative(
|
|
client,
|
|
token,
|
|
title="Carryover Unplan",
|
|
archetype_key="initiative.product",
|
|
)
|
|
initiative_id = created.json()["id"]
|
|
|
|
action = client.post(
|
|
f"/api/initiatives/{initiative_id}/actions",
|
|
json={"title": "In Arbeit", "status": "in_progress"},
|
|
headers=_auth(token),
|
|
)
|
|
action_id = action.json()["id"]
|
|
|
|
unplan = client.post(
|
|
f"/api/actions/{action_id}/unplan-to-backlog?carryover=true",
|
|
headers=_auth(token),
|
|
)
|
|
assert unplan.status_code == 200
|
|
assert unplan.json()["backlog_item"]["status"] == "accepted"
|
|
|
|
normal_unplan = client.post(
|
|
f"/api/actions/{action_id}/unplan-to-backlog",
|
|
headers=_auth(token),
|
|
)
|
|
assert normal_unplan.status_code in (400, 404)
|