All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 2m17s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 13s
Migration 020 fuer Kantenarten und Parallelgruppe; steering/graph berechnet Fortschritt; UI zeigt Bereit/Blockiert in Liste und Graph. Co-authored-by: Cursor <cursoragent@cursor.com>
245 lines
8.1 KiB
Python
245 lines
8.1 KiB
Python
"""AP1.4 — RoadmapItem & Quality Gate tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from tests.factories import provision_user_in_tenant
|
|
from tests.test_initiatives_actions import (
|
|
_auth,
|
|
_create_initiative,
|
|
_login,
|
|
)
|
|
|
|
|
|
def _create_roadmap_item(client, token, initiative_id, **kwargs):
|
|
body = {"title": "Gate A", "item_type": "milestone", **kwargs}
|
|
return client.post(
|
|
f"/api/initiatives/{initiative_id}/roadmap/items",
|
|
json=body,
|
|
headers=_auth(token),
|
|
)
|
|
|
|
|
|
def test_roadmap_item_crud(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
|
|
created = _create_roadmap_item(
|
|
client,
|
|
token,
|
|
initiative_id,
|
|
sequencing_mode="parallel",
|
|
item_type="maturity_stage",
|
|
)
|
|
assert created.status_code == 201
|
|
item = created.json()
|
|
assert item["item_type"] == "maturity_stage"
|
|
assert item["sequencing_mode"] == "parallel"
|
|
|
|
listed = client.get(
|
|
f"/api/initiatives/{initiative_id}/roadmap/items",
|
|
headers=_auth(token),
|
|
)
|
|
assert listed.status_code == 200
|
|
assert len(listed.json()) >= 1
|
|
|
|
patched = client.patch(
|
|
f"/api/roadmap-items/{item['id']}",
|
|
json={"status": "at_risk"},
|
|
headers=_auth(token),
|
|
)
|
|
assert patched.status_code == 200
|
|
assert patched.json()["status"] == "at_risk"
|
|
|
|
deleted = client.delete(f"/api/roadmap-items/{item['id']}", headers=_auth(token))
|
|
assert deleted.status_code == 204
|
|
|
|
|
|
def test_verify_reached_requires_evidence(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
item = _create_roadmap_item(client, token, initiative_id, status="active").json()
|
|
|
|
fail = client.post(
|
|
f"/api/roadmap-items/{item['id']}/verify-reached",
|
|
headers=_auth(token),
|
|
)
|
|
assert fail.status_code == 400
|
|
|
|
evidence = client.post(
|
|
f"/api/initiatives/{initiative_id}/evidence",
|
|
json={
|
|
"title": "Nachweis",
|
|
"roadmap_item_id": item["id"],
|
|
"status": "accepted",
|
|
},
|
|
headers=_auth(token),
|
|
)
|
|
assert evidence.status_code == 201
|
|
|
|
ok = client.post(
|
|
f"/api/roadmap-items/{item['id']}/verify-reached",
|
|
headers=_auth(token),
|
|
)
|
|
assert ok.status_code == 200
|
|
assert ok.json()["status"] == "reached"
|
|
assert ok.json()["verify_reason"] in ("criteria_ready", "evidence_accepted")
|
|
|
|
|
|
def test_milestone_compat_api_uses_roadmap(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
|
|
created = client.post(
|
|
f"/api/initiatives/{initiative_id}/milestones",
|
|
json={"title": "Legacy MS", "status": "planned"},
|
|
headers=_auth(token),
|
|
)
|
|
assert created.status_code == 201
|
|
ms_id = created.json()["id"]
|
|
|
|
roadmap = client.get(
|
|
f"/api/initiatives/{initiative_id}/roadmap/items",
|
|
headers=_auth(token),
|
|
).json()
|
|
assert any(i["id"] == ms_id and i["item_type"] == "milestone" for i in roadmap)
|
|
|
|
|
|
def test_steering_snapshot_reads_roadmap_items(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
_create_roadmap_item(client, token, initiative_id, status="active", target_date="2026-12-31")
|
|
|
|
snap = client.get(
|
|
f"/api/initiatives/{initiative_id}/steering-snapshot",
|
|
headers=_auth(token),
|
|
).json()
|
|
assert snap.get("roadmap_items")
|
|
assert snap.get("upcoming_milestones")
|
|
assert len(snap["upcoming_roadmap_items"]) >= 1
|
|
|
|
|
|
def test_initiative_roadmap_dependencies_list(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
gate_a = _create_roadmap_item(client, token, initiative_id, title="Gate A").json()
|
|
gate_b = _create_roadmap_item(client, token, initiative_id, title="Gate B").json()
|
|
|
|
created = client.post(
|
|
f"/api/roadmap-items/{gate_a['id']}/dependencies",
|
|
json={"to_item_id": gate_b["id"], "dependency_type": "requires"},
|
|
headers=_auth(token),
|
|
)
|
|
assert created.status_code == 201
|
|
|
|
listed = client.get(
|
|
f"/api/initiatives/{initiative_id}/roadmap/dependencies",
|
|
headers=_auth(token),
|
|
)
|
|
assert listed.status_code == 200
|
|
deps = listed.json()
|
|
assert len(deps) == 1
|
|
assert deps[0]["from_item_id"] == gate_a["id"]
|
|
assert deps[0]["to_item_id"] == gate_b["id"]
|
|
assert deps[0]["dependency_type"] == "requires"
|
|
|
|
|
|
def test_roadmap_dependency_delete(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
gate_a = _create_roadmap_item(client, token, initiative_id, title="Gate A").json()
|
|
gate_b = _create_roadmap_item(client, token, initiative_id, title="Gate B").json()
|
|
|
|
created = client.post(
|
|
f"/api/roadmap-items/{gate_a['id']}/dependencies",
|
|
json={"to_item_id": gate_b["id"], "dependency_type": "requires"},
|
|
headers=_auth(token),
|
|
)
|
|
dep_id = created.json()["id"]
|
|
|
|
deleted = client.delete(
|
|
f"/api/roadmap-dependencies/{dep_id}",
|
|
headers=_auth(token),
|
|
)
|
|
assert deleted.status_code == 204
|
|
|
|
listed = client.get(
|
|
f"/api/initiatives/{initiative_id}/roadmap/dependencies",
|
|
headers=_auth(token),
|
|
)
|
|
assert listed.json() == []
|
|
|
|
|
|
def test_roadmap_dependency_duplicate_rejected(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
gate_a = _create_roadmap_item(client, token, initiative_id, title="Gate A").json()
|
|
gate_b = _create_roadmap_item(client, token, initiative_id, title="Gate B").json()
|
|
body = {"to_item_id": gate_b["id"], "dependency_type": "requires"}
|
|
|
|
assert client.post(
|
|
f"/api/roadmap-items/{gate_a['id']}/dependencies",
|
|
json=body,
|
|
headers=_auth(token),
|
|
).status_code == 201
|
|
|
|
dup = client.post(
|
|
f"/api/roadmap-items/{gate_a['id']}/dependencies",
|
|
json=body,
|
|
headers=_auth(token),
|
|
)
|
|
assert dup.status_code == 400
|
|
assert "existiert bereits" in dup.json()["detail"]
|
|
|
|
|
|
def test_initiative_roadmap_criteria_progress(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
item = _create_roadmap_item(
|
|
client,
|
|
token,
|
|
initiative_id,
|
|
title="Gate mit Kriterium",
|
|
initial_criterion_title="Erstes Kriterium",
|
|
).json()
|
|
|
|
progress = client.get(
|
|
f"/api/initiatives/{initiative_id}/roadmap/criteria-progress",
|
|
headers=_auth(token),
|
|
)
|
|
assert progress.status_code == 200
|
|
body = progress.json()
|
|
assert body[item["id"]]["total"] >= 1
|
|
assert body[item["id"]]["closed"] == 0
|
|
|
|
|
|
def test_initiative_roadmap_graph_state(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
gate_a = _create_roadmap_item(client, token, initiative_id, title="Gate A", sort_order=0).json()
|
|
gate_b = _create_roadmap_item(client, token, initiative_id, title="Gate B", sort_order=1).json()
|
|
|
|
client.post(
|
|
f"/api/roadmap-items/{gate_a['id']}/dependencies",
|
|
json={"to_item_id": gate_b["id"], "dependency_type": "requires"},
|
|
headers=_auth(token),
|
|
)
|
|
|
|
state = client.get(
|
|
f"/api/initiatives/{initiative_id}/roadmap/graph-state",
|
|
headers=_auth(token),
|
|
)
|
|
assert state.status_code == 200
|
|
body = state.json()
|
|
assert body["items"][gate_a["id"]]["blocked"] is True
|
|
assert gate_b["id"] in body["items"][gate_a["id"]]["blocked_by"]
|
|
assert gate_a["id"] not in body["ready_items"]
|